├── .gitmodules ├── 3rdparty ├── imgui_mod │ └── meshview │ │ └── imgui │ │ ├── imconfig.h │ │ ├── imgui.cpp │ │ ├── imgui.h │ │ ├── imgui_draw.cpp │ │ ├── imgui_impl_glfw.cpp │ │ ├── imgui_impl_glfw.h │ │ ├── imgui_impl_opengl3.cpp │ │ ├── imgui_impl_opengl3.h │ │ ├── imgui_internal.h │ │ ├── imgui_stdlib.cpp │ │ ├── imgui_stdlib.h │ │ ├── imgui_widgets.cpp │ │ ├── imstb_rectpack.h │ │ ├── imstb_textedit.h │ │ └── imstb_truetype.h └── stb │ ├── stb_image.cpp │ └── stb_image.h ├── CMakeLists.txt ├── README.md ├── common.hpp.in ├── example.cpp ├── include └── meshview │ ├── internal │ ├── assert.hpp │ ├── shader.hpp │ └── shader_inline.hpp │ ├── meshview.hpp │ ├── meshview_imgui.hpp │ └── util.hpp ├── pybind.cpp ├── python_example.py ├── readme-img ├── depth-cam.png ├── example.png └── smpl-visual.png ├── sample-proj └── CMakeLists.txt ├── setup.py └── src ├── camera.cpp ├── cmake └── meshviewConfig.cmake ├── mesh.cpp ├── shader.cpp ├── texture.cpp ├── util.cpp └── viewer.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/glew"] 2 | path = 3rdparty/glew 3 | url = https://github.com/edoren/glew 4 | [submodule "3rdparty/glfw"] 5 | path = 3rdparty/glfw 6 | url = https://github.com/glfw/glfw 7 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/branch with your modifications to imconfig.h) 7 | // B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" 8 | // If you do so you need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include 9 | // the imgui*.cpp files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 10 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 11 | // 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. 12 | //----------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | //---- Define assertion handler. Defaults to calling assert(). 17 | // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. 18 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 19 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 20 | 21 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 22 | // Using dear imgui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 23 | //#define IMGUI_API __declspec( dllexport ) 24 | //#define IMGUI_API __declspec( dllimport ) 25 | 26 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 27 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 28 | 29 | //---- Disable all of Dear ImGui or don't implement standard windows. 30 | // It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. 31 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 32 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. 33 | //#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty. 34 | 35 | //---- Don't implement some functions to reduce linkage requirements. 36 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 37 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 38 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 39 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 40 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 41 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 42 | //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. 43 | //#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(). 44 | 45 | //---- Include imgui_user.h at the end of imgui.h as a convenience 46 | //#define IMGUI_INCLUDE_IMGUI_USER_H 47 | 48 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 49 | //#define IMGUI_USE_BGRA_PACKED_COLOR 50 | 51 | //---- Use 32-bit for ImWchar (default is 16-bit) to support full unicode code points. 52 | //#define IMGUI_USE_WCHAR32 53 | 54 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 55 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 56 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 57 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 58 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 59 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 60 | 61 | //---- Unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined, use the much faster STB sprintf library implementation of vsnprintf instead of the one from the default C library. 62 | // Note that stb_sprintf.h is meant to be provided by the user and available in the include path at compile time. Also, the compatibility checks of the arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf. 63 | // #define IMGUI_USE_STB_SPRINTF 64 | 65 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 66 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 67 | /* 68 | #define im_vec2_class_extra \ 69 | imvec2(const myvec2& f) { x = f.x; y = f.y; } \ 70 | operator myvec2() const { return myvec2(x,y); } 71 | 72 | #define IM_VEC4_CLASS_EXTRA \ 73 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 74 | operator MyVec4() const { return MyVec4(x,y,z,w); } 75 | */ 76 | 77 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 78 | // Your renderer back-end will need to support it (most example renderer back-ends support both 16/32-bit indices). 79 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 80 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 81 | #define ImDrawIdx unsigned int 82 | 83 | //---- Override ImDrawCallback signature (will need to modify renderer back-ends accordingly) 84 | //struct ImDrawList; 85 | //struct ImDrawCmd; 86 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 87 | //#define ImDrawCallback MyImDrawCallback 88 | 89 | //---- Debug Tools: Macro to break in Debugger 90 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 91 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 92 | //#define IM_DEBUG_BREAK __debugbreak() 93 | 94 | //---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), 95 | // (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) 96 | // This adds a small runtime cost which is why it is not enabled by default. 97 | //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX 98 | 99 | //---- Debug Tools: Enable slower asserts 100 | //#define IMGUI_DEBUG_PARANOID 101 | 102 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 103 | /* 104 | namespace ImGui 105 | { 106 | void MyFunction(const char* name, const MyMatrix44& v); 107 | } 108 | */ 109 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imgui_impl_glfw.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Binding for GLFW 2 | // This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) 3 | // (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) 4 | 5 | // Implemented features: 6 | // [X] Platform: Clipboard support. 7 | // [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. 8 | // [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW. 9 | // [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE). 10 | // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. 11 | 12 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 13 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 14 | // https://github.com/ocornut/imgui 15 | 16 | // About GLSL version: 17 | // The 'glsl_version' initialization parameter defaults to "#version 150" if NULL. 18 | // Only override if your GL version doesn't handle this GLSL version. Keep NULL if unsure! 19 | 20 | #pragma once 21 | 22 | struct GLFWwindow; 23 | 24 | IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); 25 | IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); 26 | IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); 27 | IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); 28 | 29 | // InitXXX function with 'install_callbacks=true': install GLFW callbacks. They will call user's previously installed callbacks, if any. 30 | // InitXXX function with 'install_callbacks=false': do not install GLFW callbacks. You will need to call them yourself from your own GLFW callbacks. 31 | IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); 32 | IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); 33 | IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); 34 | IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); 35 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imgui_impl_opengl3.cpp: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline 2 | // - Desktop GL: 2.x 3.x 4.x 3 | // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) 4 | // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) 5 | 6 | // Implemented features: 7 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! 8 | // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. 9 | 10 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 11 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 12 | // https://github.com/ocornut/imgui 13 | 14 | // CHANGELOG 15 | // (minor and older changes stripped away, please see git history for details) 16 | // 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. 17 | // 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix. 18 | // 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset. 19 | // 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader. 20 | // 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader. 21 | // 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. 22 | // 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. 23 | // 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. 24 | // 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. 25 | // 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. 26 | // 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. 27 | // 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. 28 | // 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). 29 | // 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. 30 | // 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. 31 | // 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). 32 | // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. 33 | // 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. 34 | // 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. 35 | // 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". 36 | // 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. 37 | // 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. 38 | // 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. 39 | // 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. 40 | // 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. 41 | // 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. 42 | // 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". 43 | // 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. 44 | // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. 45 | // 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. 46 | // 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. 47 | // 2017-05-01: OpenGL: Fixed save and restore of current blend func state. 48 | // 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. 49 | // 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. 50 | // 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) 51 | 52 | //---------------------------------------- 53 | // OpenGL GLSL GLSL 54 | // version version string 55 | //---------------------------------------- 56 | // 2.0 110 "#version 110" 57 | // 2.1 120 "#version 120" 58 | // 3.0 130 "#version 130" 59 | // 3.1 140 "#version 140" 60 | // 3.2 150 "#version 150" 61 | // 3.3 330 "#version 330 core" 62 | // 4.0 400 "#version 400 core" 63 | // 4.1 410 "#version 410 core" 64 | // 4.2 420 "#version 410 core" 65 | // 4.3 430 "#version 430 core" 66 | // ES 2.0 100 "#version 100" = WebGL 1.0 67 | // ES 3.0 300 "#version 300 es" = WebGL 2.0 68 | //---------------------------------------- 69 | 70 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 71 | #define _CRT_SECURE_NO_WARNINGS 72 | #endif 73 | 74 | #include "imgui.h" 75 | #include "imgui_impl_opengl3.h" 76 | #include 77 | #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier 78 | #include // intptr_t 79 | #else 80 | #include // intptr_t 81 | #endif 82 | #if defined(__APPLE__) 83 | #include "TargetConditionals.h" 84 | #endif 85 | 86 | // Auto-enable GLES on matching platforms 87 | #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) 88 | #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) 89 | #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" 90 | #elif defined(__EMSCRIPTEN__) 91 | #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" 92 | #endif 93 | #endif 94 | 95 | #if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) 96 | #undef IMGUI_IMPL_OPENGL_LOADER_GL3W 97 | #undef IMGUI_IMPL_OPENGL_LOADER_GLEW 98 | #undef IMGUI_IMPL_OPENGL_LOADER_GLAD 99 | #undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING2 100 | #undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING3 101 | #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM 102 | #endif 103 | 104 | // GL includes 105 | #if defined(IMGUI_IMPL_OPENGL_ES2) 106 | #include 107 | #elif defined(IMGUI_IMPL_OPENGL_ES3) 108 | #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) 109 | #include // Use GL ES 3 110 | #else 111 | #include // Use GL ES 3 112 | #endif 113 | #else 114 | // About Desktop OpenGL function loaders: 115 | // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. 116 | // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). 117 | // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. 118 | #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) 119 | #include // Needs to be initialized with gl3wInit() in user's code 120 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) 121 | #include // Needs to be initialized with glewInit() in user's code. 122 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) 123 | #include // Needs to be initialized with gladLoadGL() in user's code. 124 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) 125 | #ifndef GLFW_INCLUDE_NONE 126 | #define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. 127 | #endif 128 | #include // Needs to be initialized with glbinding::Binding::initialize() in user's code. 129 | #include 130 | using namespace gl; 131 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) 132 | #ifndef GLFW_INCLUDE_NONE 133 | #define GLFW_INCLUDE_NONE // GLFW including OpenGL headers causes ambiguity or multiple definition errors. 134 | #endif 135 | #include // Needs to be initialized with glbinding::initialize() in user's code. 136 | #include 137 | using namespace gl; 138 | #else 139 | #include IMGUI_IMPL_OPENGL_LOADER_CUSTOM 140 | #endif 141 | #endif 142 | 143 | // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. 144 | #if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2) 145 | #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 0 146 | #else 147 | #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 1 148 | #endif 149 | 150 | // OpenGL Data 151 | static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) 152 | static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. 153 | static GLuint g_FontTexture = 0; 154 | static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; 155 | static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location 156 | static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location 157 | static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; 158 | 159 | // Functions 160 | bool ImGui_ImplOpenGL3_Init(const char* glsl_version) 161 | { 162 | // Query for GL version (e.g. 320 for GL 3.2) 163 | #if !defined(IMGUI_IMPL_OPENGL_ES2) 164 | GLint major, minor; 165 | glGetIntegerv(GL_MAJOR_VERSION, &major); 166 | glGetIntegerv(GL_MINOR_VERSION, &minor); 167 | g_GlVersion = major * 100 + minor * 10; 168 | #else 169 | g_GlVersion = 200; // GLES 2 170 | #endif 171 | 172 | // Setup back-end capabilities flags 173 | ImGuiIO& io = ImGui::GetIO(); 174 | io.BackendRendererName = "imgui_impl_opengl3"; 175 | #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 176 | if (g_GlVersion >= 320) 177 | io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. 178 | #endif 179 | 180 | // Store GLSL version string so we can refer to it later in case we recreate shaders. 181 | // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. 182 | #if defined(IMGUI_IMPL_OPENGL_ES2) 183 | if (glsl_version == NULL) 184 | glsl_version = "#version 100"; 185 | #elif defined(IMGUI_IMPL_OPENGL_ES3) 186 | if (glsl_version == NULL) 187 | glsl_version = "#version 300 es"; 188 | #elif defined(__APPLE__) 189 | if (glsl_version == NULL) 190 | glsl_version = "#version 150"; 191 | #else 192 | if (glsl_version == NULL) 193 | glsl_version = "#version 130"; 194 | #endif 195 | IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); 196 | strcpy(g_GlslVersionString, glsl_version); 197 | strcat(g_GlslVersionString, "\n"); 198 | 199 | // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. 200 | // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! 201 | // If auto-detection fails or doesn't select the same GL loader file as used by your application, 202 | // you are likely to get a crash below. 203 | // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. 204 | const char* gl_loader = "Unknown"; 205 | IM_UNUSED(gl_loader); 206 | #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) 207 | gl_loader = "GL3W"; 208 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) 209 | gl_loader = "GLEW"; 210 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) 211 | gl_loader = "GLAD"; 212 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) 213 | gl_loader = "glbinding2"; 214 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) 215 | gl_loader = "glbinding3"; 216 | #elif defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) 217 | gl_loader = "custom"; 218 | #else 219 | gl_loader = "none"; 220 | #endif 221 | 222 | // Make a dummy GL call (we don't actually need the result) 223 | // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. 224 | // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. 225 | GLint current_texture; 226 | glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); 227 | 228 | return true; 229 | } 230 | 231 | void ImGui_ImplOpenGL3_Shutdown() 232 | { 233 | ImGui_ImplOpenGL3_DestroyDeviceObjects(); 234 | } 235 | 236 | void ImGui_ImplOpenGL3_NewFrame() 237 | { 238 | if (!g_ShaderHandle) 239 | ImGui_ImplOpenGL3_CreateDeviceObjects(); 240 | } 241 | 242 | static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) 243 | { 244 | // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill 245 | glEnable(GL_BLEND); 246 | glBlendEquation(GL_FUNC_ADD); 247 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 248 | glDisable(GL_CULL_FACE); 249 | glDisable(GL_DEPTH_TEST); 250 | glEnable(GL_SCISSOR_TEST); 251 | #ifdef GL_POLYGON_MODE 252 | glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 253 | #endif 254 | 255 | // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) 256 | bool clip_origin_lower_left = true; 257 | #if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) 258 | GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); 259 | if (current_clip_origin == GL_UPPER_LEFT) 260 | clip_origin_lower_left = false; 261 | #endif 262 | 263 | // Setup viewport, orthographic projection matrix 264 | // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. 265 | glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); 266 | float L = draw_data->DisplayPos.x; 267 | float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; 268 | float T = draw_data->DisplayPos.y; 269 | float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; 270 | if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left 271 | const float ortho_projection[4][4] = 272 | { 273 | { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, 274 | { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, 275 | { 0.0f, 0.0f, -1.0f, 0.0f }, 276 | { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, 277 | }; 278 | glUseProgram(g_ShaderHandle); 279 | glUniform1i(g_AttribLocationTex, 0); 280 | glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); 281 | #ifdef GL_SAMPLER_BINDING 282 | glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. 283 | #endif 284 | 285 | (void)vertex_array_object; 286 | #ifndef IMGUI_IMPL_OPENGL_ES2 287 | glBindVertexArray(vertex_array_object); 288 | #endif 289 | 290 | // Bind vertex/index buffers and setup attributes for ImDrawVert 291 | glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); 292 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); 293 | glEnableVertexAttribArray(g_AttribLocationVtxPos); 294 | glEnableVertexAttribArray(g_AttribLocationVtxUV); 295 | glEnableVertexAttribArray(g_AttribLocationVtxColor); 296 | glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); 297 | glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); 298 | glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); 299 | } 300 | 301 | // OpenGL3 Render function. 302 | // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) 303 | // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. 304 | void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) 305 | { 306 | // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) 307 | int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); 308 | int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); 309 | if (fb_width <= 0 || fb_height <= 0) 310 | return; 311 | 312 | // Backup GL state 313 | GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); 314 | glActiveTexture(GL_TEXTURE0); 315 | GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); 316 | GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 317 | #ifdef GL_SAMPLER_BINDING 318 | GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); 319 | #endif 320 | GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); 321 | #ifndef IMGUI_IMPL_OPENGL_ES2 322 | GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); 323 | #endif 324 | #ifdef GL_POLYGON_MODE 325 | GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); 326 | #endif 327 | GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); 328 | GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); 329 | GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); 330 | GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); 331 | GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); 332 | GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); 333 | GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); 334 | GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); 335 | GLboolean last_enable_blend = glIsEnabled(GL_BLEND); 336 | GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); 337 | GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); 338 | GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); 339 | 340 | // Setup desired GL state 341 | // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) 342 | // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. 343 | GLuint vertex_array_object = 0; 344 | #ifndef IMGUI_IMPL_OPENGL_ES2 345 | glGenVertexArrays(1, &vertex_array_object); 346 | #endif 347 | ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); 348 | 349 | // Will project scissor/clipping rectangles into framebuffer space 350 | ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports 351 | ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) 352 | 353 | // Render command lists 354 | for (int n = 0; n < draw_data->CmdListsCount; n++) 355 | { 356 | const ImDrawList* cmd_list = draw_data->CmdLists[n]; 357 | 358 | // Upload vertex/index buffers 359 | glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); 360 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); 361 | 362 | for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) 363 | { 364 | const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; 365 | if (pcmd->UserCallback != NULL) 366 | { 367 | // User callback, registered via ImDrawList::AddCallback() 368 | // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) 369 | if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) 370 | ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); 371 | else 372 | pcmd->UserCallback(cmd_list, pcmd); 373 | } 374 | else 375 | { 376 | // Project scissor/clipping rectangles into framebuffer space 377 | ImVec4 clip_rect; 378 | clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; 379 | clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; 380 | clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; 381 | clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; 382 | 383 | if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) 384 | { 385 | // Apply scissor/clipping rectangle 386 | 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)); 387 | 388 | // Bind texture, Draw 389 | glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); 390 | #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 391 | if (g_GlVersion >= 320) 392 | glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); 393 | else 394 | #endif 395 | glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); 396 | } 397 | } 398 | } 399 | } 400 | 401 | // Destroy the temporary VAO 402 | #ifndef IMGUI_IMPL_OPENGL_ES2 403 | glDeleteVertexArrays(1, &vertex_array_object); 404 | #endif 405 | 406 | // Restore modified GL state 407 | glUseProgram(last_program); 408 | glBindTexture(GL_TEXTURE_2D, last_texture); 409 | #ifdef GL_SAMPLER_BINDING 410 | glBindSampler(0, last_sampler); 411 | #endif 412 | glActiveTexture(last_active_texture); 413 | #ifndef IMGUI_IMPL_OPENGL_ES2 414 | glBindVertexArray(last_vertex_array_object); 415 | #endif 416 | glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); 417 | glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); 418 | glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); 419 | if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); 420 | if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); 421 | if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); 422 | if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); 423 | #ifdef GL_POLYGON_MODE 424 | glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); 425 | #endif 426 | glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); 427 | glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); 428 | } 429 | 430 | bool ImGui_ImplOpenGL3_CreateFontsTexture() 431 | { 432 | // Build texture atlas 433 | ImGuiIO& io = ImGui::GetIO(); 434 | unsigned char* pixels; 435 | int width, height; 436 | io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (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. 437 | 438 | // Upload texture to graphics system 439 | GLint last_texture; 440 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 441 | glGenTextures(1, &g_FontTexture); 442 | glBindTexture(GL_TEXTURE_2D, g_FontTexture); 443 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 444 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 445 | #ifdef GL_UNPACK_ROW_LENGTH 446 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 447 | #endif 448 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 449 | 450 | // Store our identifier 451 | io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; 452 | 453 | // Restore state 454 | glBindTexture(GL_TEXTURE_2D, last_texture); 455 | 456 | return true; 457 | } 458 | 459 | void ImGui_ImplOpenGL3_DestroyFontsTexture() 460 | { 461 | if (g_FontTexture) 462 | { 463 | ImGuiIO& io = ImGui::GetIO(); 464 | glDeleteTextures(1, &g_FontTexture); 465 | io.Fonts->TexID = 0; 466 | g_FontTexture = 0; 467 | } 468 | } 469 | 470 | // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. 471 | static bool CheckShader(GLuint handle, const char* desc) 472 | { 473 | GLint status = 0, log_length = 0; 474 | glGetShaderiv(handle, GL_COMPILE_STATUS, &status); 475 | glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); 476 | if ((GLboolean)status == GL_FALSE) 477 | fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); 478 | if (log_length > 1) 479 | { 480 | ImVector buf; 481 | buf.resize((int)(log_length + 1)); 482 | glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); 483 | fprintf(stderr, "%s\n", buf.begin()); 484 | } 485 | return (GLboolean)status == GL_TRUE; 486 | } 487 | 488 | // If you get an error please report on GitHub. You may try different GL context version or GLSL version. 489 | static bool CheckProgram(GLuint handle, const char* desc) 490 | { 491 | GLint status = 0, log_length = 0; 492 | glGetProgramiv(handle, GL_LINK_STATUS, &status); 493 | glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); 494 | if ((GLboolean)status == GL_FALSE) 495 | fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); 496 | if (log_length > 1) 497 | { 498 | ImVector buf; 499 | buf.resize((int)(log_length + 1)); 500 | glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); 501 | fprintf(stderr, "%s\n", buf.begin()); 502 | } 503 | return (GLboolean)status == GL_TRUE; 504 | } 505 | 506 | bool ImGui_ImplOpenGL3_CreateDeviceObjects() 507 | { 508 | // Backup GL state 509 | GLint last_texture, last_array_buffer; 510 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); 511 | glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); 512 | #ifndef IMGUI_IMPL_OPENGL_ES2 513 | GLint last_vertex_array; 514 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); 515 | #endif 516 | 517 | // Parse GLSL version string 518 | int glsl_version = 130; 519 | sscanf(g_GlslVersionString, "#version %d", &glsl_version); 520 | 521 | const GLchar* vertex_shader_glsl_120 = 522 | "uniform mat4 ProjMtx;\n" 523 | "attribute vec2 Position;\n" 524 | "attribute vec2 UV;\n" 525 | "attribute vec4 Color;\n" 526 | "varying vec2 Frag_UV;\n" 527 | "varying vec4 Frag_Color;\n" 528 | "void main()\n" 529 | "{\n" 530 | " Frag_UV = UV;\n" 531 | " Frag_Color = Color;\n" 532 | " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" 533 | "}\n"; 534 | 535 | const GLchar* vertex_shader_glsl_130 = 536 | "uniform mat4 ProjMtx;\n" 537 | "in vec2 Position;\n" 538 | "in vec2 UV;\n" 539 | "in vec4 Color;\n" 540 | "out vec2 Frag_UV;\n" 541 | "out vec4 Frag_Color;\n" 542 | "void main()\n" 543 | "{\n" 544 | " Frag_UV = UV;\n" 545 | " Frag_Color = Color;\n" 546 | " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" 547 | "}\n"; 548 | 549 | const GLchar* vertex_shader_glsl_300_es = 550 | "precision mediump float;\n" 551 | "layout (location = 0) in vec2 Position;\n" 552 | "layout (location = 1) in vec2 UV;\n" 553 | "layout (location = 2) in vec4 Color;\n" 554 | "uniform mat4 ProjMtx;\n" 555 | "out vec2 Frag_UV;\n" 556 | "out vec4 Frag_Color;\n" 557 | "void main()\n" 558 | "{\n" 559 | " Frag_UV = UV;\n" 560 | " Frag_Color = Color;\n" 561 | " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" 562 | "}\n"; 563 | 564 | const GLchar* vertex_shader_glsl_410_core = 565 | "layout (location = 0) in vec2 Position;\n" 566 | "layout (location = 1) in vec2 UV;\n" 567 | "layout (location = 2) in vec4 Color;\n" 568 | "uniform mat4 ProjMtx;\n" 569 | "out vec2 Frag_UV;\n" 570 | "out vec4 Frag_Color;\n" 571 | "void main()\n" 572 | "{\n" 573 | " Frag_UV = UV;\n" 574 | " Frag_Color = Color;\n" 575 | " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" 576 | "}\n"; 577 | 578 | const GLchar* fragment_shader_glsl_120 = 579 | "#ifdef GL_ES\n" 580 | " precision mediump float;\n" 581 | "#endif\n" 582 | "uniform sampler2D Texture;\n" 583 | "varying vec2 Frag_UV;\n" 584 | "varying vec4 Frag_Color;\n" 585 | "void main()\n" 586 | "{\n" 587 | " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" 588 | "}\n"; 589 | 590 | const GLchar* fragment_shader_glsl_130 = 591 | "uniform sampler2D Texture;\n" 592 | "in vec2 Frag_UV;\n" 593 | "in vec4 Frag_Color;\n" 594 | "out vec4 Out_Color;\n" 595 | "void main()\n" 596 | "{\n" 597 | " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" 598 | "}\n"; 599 | 600 | const GLchar* fragment_shader_glsl_300_es = 601 | "precision mediump float;\n" 602 | "uniform sampler2D Texture;\n" 603 | "in vec2 Frag_UV;\n" 604 | "in vec4 Frag_Color;\n" 605 | "layout (location = 0) out vec4 Out_Color;\n" 606 | "void main()\n" 607 | "{\n" 608 | " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" 609 | "}\n"; 610 | 611 | const GLchar* fragment_shader_glsl_410_core = 612 | "in vec2 Frag_UV;\n" 613 | "in vec4 Frag_Color;\n" 614 | "uniform sampler2D Texture;\n" 615 | "layout (location = 0) out vec4 Out_Color;\n" 616 | "void main()\n" 617 | "{\n" 618 | " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" 619 | "}\n"; 620 | 621 | // Select shaders matching our GLSL versions 622 | const GLchar* vertex_shader = NULL; 623 | const GLchar* fragment_shader = NULL; 624 | if (glsl_version < 130) 625 | { 626 | vertex_shader = vertex_shader_glsl_120; 627 | fragment_shader = fragment_shader_glsl_120; 628 | } 629 | else if (glsl_version >= 410) 630 | { 631 | vertex_shader = vertex_shader_glsl_410_core; 632 | fragment_shader = fragment_shader_glsl_410_core; 633 | } 634 | else if (glsl_version == 300) 635 | { 636 | vertex_shader = vertex_shader_glsl_300_es; 637 | fragment_shader = fragment_shader_glsl_300_es; 638 | } 639 | else 640 | { 641 | vertex_shader = vertex_shader_glsl_130; 642 | fragment_shader = fragment_shader_glsl_130; 643 | } 644 | 645 | // Create shaders 646 | const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; 647 | g_VertHandle = glCreateShader(GL_VERTEX_SHADER); 648 | glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); 649 | glCompileShader(g_VertHandle); 650 | CheckShader(g_VertHandle, "vertex shader"); 651 | 652 | const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; 653 | g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); 654 | glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); 655 | glCompileShader(g_FragHandle); 656 | CheckShader(g_FragHandle, "fragment shader"); 657 | 658 | g_ShaderHandle = glCreateProgram(); 659 | glAttachShader(g_ShaderHandle, g_VertHandle); 660 | glAttachShader(g_ShaderHandle, g_FragHandle); 661 | glLinkProgram(g_ShaderHandle); 662 | CheckProgram(g_ShaderHandle, "shader program"); 663 | 664 | g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); 665 | g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); 666 | g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); 667 | g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); 668 | g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); 669 | 670 | // Create buffers 671 | glGenBuffers(1, &g_VboHandle); 672 | glGenBuffers(1, &g_ElementsHandle); 673 | 674 | ImGui_ImplOpenGL3_CreateFontsTexture(); 675 | 676 | // Restore modified GL state 677 | glBindTexture(GL_TEXTURE_2D, last_texture); 678 | glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); 679 | #ifndef IMGUI_IMPL_OPENGL_ES2 680 | glBindVertexArray(last_vertex_array); 681 | #endif 682 | 683 | return true; 684 | } 685 | 686 | void ImGui_ImplOpenGL3_DestroyDeviceObjects() 687 | { 688 | if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } 689 | if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } 690 | if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } 691 | if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } 692 | if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } 693 | if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } 694 | if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } 695 | 696 | ImGui_ImplOpenGL3_DestroyFontsTexture(); 697 | } 698 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imgui_impl_opengl3.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline 2 | // - Desktop GL: 2.x 3.x 4.x 3 | // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) 4 | // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) 5 | 6 | // Implemented features: 7 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! 8 | // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. 9 | 10 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 11 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 12 | // https://github.com/ocornut/imgui 13 | 14 | // About Desktop OpenGL function loaders: 15 | // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. 16 | // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). 17 | // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. 18 | 19 | // About GLSL version: 20 | // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. 21 | // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" 22 | // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. 23 | 24 | #pragma once 25 | #include "imgui.h" // IMGUI_IMPL_API 26 | 27 | // Backend API 28 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); 29 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); 30 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); 31 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); 32 | 33 | // (Optional) Called by Init/NewFrame/Shutdown 34 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); 35 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); 36 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); 37 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); 38 | 39 | // Specific OpenGL versions 40 | //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten 41 | //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android 42 | 43 | // Desktop OpenGL: attempt to detect default GL loader based on available header files. 44 | // If auto-detection fails or doesn't select the same GL loader file as used by your application, 45 | // you are likely to get a crash in ImGui_ImplOpenGL3_Init(). 46 | // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. 47 | #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ 48 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ 49 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ 50 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING2) \ 51 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING3) \ 52 | && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) 53 | #if defined(__has_include) 54 | #if __has_include() 55 | #define IMGUI_IMPL_OPENGL_LOADER_GLEW 56 | #elif __has_include() 57 | #define IMGUI_IMPL_OPENGL_LOADER_GLAD 58 | #elif __has_include() 59 | #define IMGUI_IMPL_OPENGL_LOADER_GL3W 60 | #elif __has_include() 61 | #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING3 62 | #elif __has_include() 63 | #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING2 64 | #else 65 | #error "Cannot detect OpenGL loader!" 66 | #endif 67 | #else 68 | #define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W 69 | #endif 70 | #endif 71 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imgui_stdlib.cpp: -------------------------------------------------------------------------------- 1 | // imgui_stdlib.cpp 2 | // Wrappers for C++ standard library (STL) types (std::string, etc.) 3 | // This is also an example of how you may wrap your own similar types. 4 | 5 | // Compatibility: 6 | // - std::string support is only guaranteed to work from C++11. 7 | // If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) 8 | 9 | // Changelog: 10 | // - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string 11 | 12 | #include "imgui.h" 13 | #include "imgui_stdlib.h" 14 | 15 | struct InputTextCallback_UserData 16 | { 17 | std::string* Str; 18 | ImGuiInputTextCallback ChainCallback; 19 | void* ChainCallbackUserData; 20 | }; 21 | 22 | static int InputTextCallback(ImGuiInputTextCallbackData* data) 23 | { 24 | InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; 25 | if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) 26 | { 27 | // Resize string callback 28 | // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. 29 | std::string* str = user_data->Str; 30 | IM_ASSERT(data->Buf == str->c_str()); 31 | str->resize(data->BufTextLen); 32 | data->Buf = (char*)str->c_str(); 33 | } 34 | else if (user_data->ChainCallback) 35 | { 36 | // Forward to user callback, if any 37 | data->UserData = user_data->ChainCallbackUserData; 38 | return user_data->ChainCallback(data); 39 | } 40 | return 0; 41 | } 42 | 43 | bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) 44 | { 45 | IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); 46 | flags |= ImGuiInputTextFlags_CallbackResize; 47 | 48 | InputTextCallback_UserData cb_user_data; 49 | cb_user_data.Str = str; 50 | cb_user_data.ChainCallback = callback; 51 | cb_user_data.ChainCallbackUserData = user_data; 52 | return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); 53 | } 54 | 55 | bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) 56 | { 57 | IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); 58 | flags |= ImGuiInputTextFlags_CallbackResize; 59 | 60 | InputTextCallback_UserData cb_user_data; 61 | cb_user_data.Str = str; 62 | cb_user_data.ChainCallback = callback; 63 | cb_user_data.ChainCallbackUserData = user_data; 64 | return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); 65 | } 66 | 67 | bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) 68 | { 69 | IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); 70 | flags |= ImGuiInputTextFlags_CallbackResize; 71 | 72 | InputTextCallback_UserData cb_user_data; 73 | cb_user_data.Str = str; 74 | cb_user_data.ChainCallback = callback; 75 | cb_user_data.ChainCallbackUserData = user_data; 76 | return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); 77 | } 78 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imgui_stdlib.h: -------------------------------------------------------------------------------- 1 | // imgui_stdlib.h 2 | // Wrappers for C++ standard library (STL) types (std::string, etc.) 3 | // This is also an example of how you may wrap your own similar types. 4 | 5 | // Compatibility: 6 | // - std::string support is only guaranteed to work from C++11. 7 | // If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) 8 | 9 | // Changelog: 10 | // - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | namespace ImGui 17 | { 18 | // ImGui::InputText() with std::string 19 | // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity 20 | IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); 21 | IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); 22 | IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); 23 | } 24 | -------------------------------------------------------------------------------- /3rdparty/imgui_mod/meshview/imgui/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.00. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Added STBRP__CDECL 5 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_rect_pack.h - v1.00 - public domain - rectangle packing 8 | // Sean Barrett 2014 9 | // 10 | // Useful for e.g. packing rectangular textures into an atlas. 11 | // Does not do rotation. 12 | // 13 | // Not necessarily the awesomest packing method, but better than 14 | // the totally naive one in stb_truetype (which is primarily what 15 | // this is meant to replace). 16 | // 17 | // Has only had a few tests run, may have issues. 18 | // 19 | // More docs to come. 20 | // 21 | // No memory allocations; uses qsort() and assert() from stdlib. 22 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 23 | // 24 | // This library currently uses the Skyline Bottom-Left algorithm. 25 | // 26 | // Please note: better rectangle packers are welcome! Please 27 | // implement them to the same API, but with a different init 28 | // function. 29 | // 30 | // Credits 31 | // 32 | // Library 33 | // Sean Barrett 34 | // Minor features 35 | // Martins Mozeiko 36 | // github:IntellectualKitty 37 | // 38 | // Bugfixes / warning fixes 39 | // Jeremy Jaussaud 40 | // Fabian Giesen 41 | // 42 | // Version history: 43 | // 44 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 45 | // 0.99 (2019-02-07) warning fixes 46 | // 0.11 (2017-03-03) return packing success/fail result 47 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 48 | // 0.09 (2016-08-27) fix compiler warnings 49 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 50 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 51 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 52 | // 0.05: added STBRP_ASSERT to allow replacing assert 53 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 54 | // 0.01: initial release 55 | // 56 | // LICENSE 57 | // 58 | // See end of file for license information. 59 | 60 | ////////////////////////////////////////////////////////////////////////////// 61 | // 62 | // INCLUDE SECTION 63 | // 64 | 65 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 66 | #define STB_INCLUDE_STB_RECT_PACK_H 67 | 68 | #define STB_RECT_PACK_VERSION 1 69 | 70 | #ifdef STBRP_STATIC 71 | #define STBRP_DEF static 72 | #else 73 | #define STBRP_DEF extern 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | extern "C" { 78 | #endif 79 | 80 | typedef struct stbrp_context stbrp_context; 81 | typedef struct stbrp_node stbrp_node; 82 | typedef struct stbrp_rect stbrp_rect; 83 | 84 | #ifdef STBRP_LARGE_RECTS 85 | typedef int stbrp_coord; 86 | #else 87 | typedef unsigned short stbrp_coord; 88 | #endif 89 | 90 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 91 | // Assign packed locations to rectangles. The rectangles are of type 92 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 93 | // are 'num_rects' many of them. 94 | // 95 | // Rectangles which are successfully packed have the 'was_packed' flag 96 | // set to a non-zero value and 'x' and 'y' store the minimum location 97 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 98 | // if you imagine y increasing downwards). Rectangles which do not fit 99 | // have the 'was_packed' flag set to 0. 100 | // 101 | // You should not try to access the 'rects' array from another thread 102 | // while this function is running, as the function temporarily reorders 103 | // the array while it executes. 104 | // 105 | // To pack into another rectangle, you need to call stbrp_init_target 106 | // again. To continue packing into the same rectangle, you can call 107 | // this function again. Calling this multiple times with multiple rect 108 | // arrays will probably produce worse packing results than calling it 109 | // a single time with the full rectangle array, but the option is 110 | // available. 111 | // 112 | // The function returns 1 if all of the rectangles were successfully 113 | // packed and 0 otherwise. 114 | 115 | struct stbrp_rect 116 | { 117 | // reserved for your use: 118 | int id; 119 | 120 | // input: 121 | stbrp_coord w, h; 122 | 123 | // output: 124 | stbrp_coord x, y; 125 | int was_packed; // non-zero if valid packing 126 | 127 | }; // 16 bytes, nominally 128 | 129 | 130 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 131 | // Initialize a rectangle packer to: 132 | // pack a rectangle that is 'width' by 'height' in dimensions 133 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 134 | // 135 | // You must call this function every time you start packing into a new target. 136 | // 137 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 138 | // the following stbrp_pack_rects() call (or calls), but can be freed after 139 | // the call (or calls) finish. 140 | // 141 | // Note: to guarantee best results, either: 142 | // 1. make sure 'num_nodes' >= 'width' 143 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 144 | // 145 | // If you don't do either of the above things, widths will be quantized to multiples 146 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 147 | // 148 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 149 | // may run out of temporary storage and be unable to pack some rectangles. 150 | 151 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 152 | // Optionally call this function after init but before doing any packing to 153 | // change the handling of the out-of-temp-memory scenario, described above. 154 | // If you call init again, this will be reset to the default (false). 155 | 156 | 157 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 158 | // Optionally select which packing heuristic the library should use. Different 159 | // heuristics will produce better/worse results for different data sets. 160 | // If you call init again, this will be reset to the default. 161 | 162 | enum 163 | { 164 | STBRP_HEURISTIC_Skyline_default=0, 165 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 166 | STBRP_HEURISTIC_Skyline_BF_sortHeight 167 | }; 168 | 169 | 170 | ////////////////////////////////////////////////////////////////////////////// 171 | // 172 | // the details of the following structures don't matter to you, but they must 173 | // be visible so you can handle the memory allocations for them 174 | 175 | struct stbrp_node 176 | { 177 | stbrp_coord x,y; 178 | stbrp_node *next; 179 | }; 180 | 181 | struct stbrp_context 182 | { 183 | int width; 184 | int height; 185 | int align; 186 | int init_mode; 187 | int heuristic; 188 | int num_nodes; 189 | stbrp_node *active_head; 190 | stbrp_node *free_head; 191 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 192 | }; 193 | 194 | #ifdef __cplusplus 195 | } 196 | #endif 197 | 198 | #endif 199 | 200 | ////////////////////////////////////////////////////////////////////////////// 201 | // 202 | // IMPLEMENTATION SECTION 203 | // 204 | 205 | #ifdef STB_RECT_PACK_IMPLEMENTATION 206 | #ifndef STBRP_SORT 207 | #include 208 | #define STBRP_SORT qsort 209 | #endif 210 | 211 | #ifndef STBRP_ASSERT 212 | #include 213 | #define STBRP_ASSERT assert 214 | #endif 215 | 216 | // [DEAR IMGUI] Added STBRP__CDECL 217 | #ifdef _MSC_VER 218 | #define STBRP__NOTUSED(v) (void)(v) 219 | #define STBRP__CDECL __cdecl 220 | #else 221 | #define STBRP__NOTUSED(v) (void)sizeof(v) 222 | #define STBRP__CDECL 223 | #endif 224 | 225 | enum 226 | { 227 | STBRP__INIT_skyline = 1 228 | }; 229 | 230 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 231 | { 232 | switch (context->init_mode) { 233 | case STBRP__INIT_skyline: 234 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 235 | context->heuristic = heuristic; 236 | break; 237 | default: 238 | STBRP_ASSERT(0); 239 | } 240 | } 241 | 242 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 243 | { 244 | if (allow_out_of_mem) 245 | // if it's ok to run out of memory, then don't bother aligning them; 246 | // this gives better packing, but may fail due to OOM (even though 247 | // the rectangles easily fit). @TODO a smarter approach would be to only 248 | // quantize once we've hit OOM, then we could get rid of this parameter. 249 | context->align = 1; 250 | else { 251 | // if it's not ok to run out of memory, then quantize the widths 252 | // so that num_nodes is always enough nodes. 253 | // 254 | // I.e. num_nodes * align >= width 255 | // align >= width / num_nodes 256 | // align = ceil(width/num_nodes) 257 | 258 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 259 | } 260 | } 261 | 262 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 263 | { 264 | int i; 265 | #ifndef STBRP_LARGE_RECTS 266 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 267 | #endif 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | #ifdef STBRP_LARGE_RECTS 287 | context->extra[1].y = (1<<30); 288 | #else 289 | context->extra[1].y = 65535; 290 | #endif 291 | context->extra[1].next = NULL; 292 | } 293 | 294 | // find minimum y position if it starts at x1 295 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 296 | { 297 | stbrp_node *node = first; 298 | int x1 = x0 + width; 299 | int min_y, visited_width, waste_area; 300 | 301 | STBRP__NOTUSED(c); 302 | 303 | STBRP_ASSERT(first->x <= x0); 304 | 305 | #if 0 306 | // skip in case we're past the node 307 | while (node->next->x <= x0) 308 | ++node; 309 | #else 310 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 311 | #endif 312 | 313 | STBRP_ASSERT(node->x <= x0); 314 | 315 | min_y = 0; 316 | waste_area = 0; 317 | visited_width = 0; 318 | while (node->x < x1) { 319 | if (node->y > min_y) { 320 | // raise min_y higher. 321 | // we've accounted for all waste up to min_y, 322 | // but we'll now add more waste for everything we've visted 323 | waste_area += visited_width * (node->y - min_y); 324 | min_y = node->y; 325 | // the first time through, visited_width might be reduced 326 | if (node->x < x0) 327 | visited_width += node->next->x - x0; 328 | else 329 | visited_width += node->next->x - node->x; 330 | } else { 331 | // add waste area 332 | int under_width = node->next->x - node->x; 333 | if (under_width + visited_width > width) 334 | under_width = width - visited_width; 335 | waste_area += under_width * (min_y - node->y); 336 | visited_width += under_width; 337 | } 338 | node = node->next; 339 | } 340 | 341 | *pwaste = waste_area; 342 | return min_y; 343 | } 344 | 345 | typedef struct 346 | { 347 | int x,y; 348 | stbrp_node **prev_link; 349 | } stbrp__findresult; 350 | 351 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 352 | { 353 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 354 | stbrp__findresult fr; 355 | stbrp_node **prev, *node, *tail, **best = NULL; 356 | 357 | // align to multiple of c->align 358 | width = (width + c->align - 1); 359 | width -= width % c->align; 360 | STBRP_ASSERT(width % c->align == 0); 361 | 362 | // if it can't possibly fit, bail immediately 363 | if (width > c->width || height > c->height) { 364 | fr.prev_link = NULL; 365 | fr.x = fr.y = 0; 366 | return fr; 367 | } 368 | 369 | node = c->active_head; 370 | prev = &c->active_head; 371 | while (node->x + width <= c->width) { 372 | int y,waste; 373 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 374 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 375 | // bottom left 376 | if (y < best_y) { 377 | best_y = y; 378 | best = prev; 379 | } 380 | } else { 381 | // best-fit 382 | if (y + height <= c->height) { 383 | // can only use it if it first vertically 384 | if (y < best_y || (y == best_y && waste < best_waste)) { 385 | best_y = y; 386 | best_waste = waste; 387 | best = prev; 388 | } 389 | } 390 | } 391 | prev = &node->next; 392 | node = node->next; 393 | } 394 | 395 | best_x = (best == NULL) ? 0 : (*best)->x; 396 | 397 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 398 | // 399 | // e.g, if fitting 400 | // 401 | // ____________________ 402 | // |____________________| 403 | // 404 | // into 405 | // 406 | // | | 407 | // | ____________| 408 | // |____________| 409 | // 410 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 411 | // 412 | // This makes BF take about 2x the time 413 | 414 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 415 | tail = c->active_head; 416 | node = c->active_head; 417 | prev = &c->active_head; 418 | // find first node that's admissible 419 | while (tail->x < width) 420 | tail = tail->next; 421 | while (tail) { 422 | int xpos = tail->x - width; 423 | int y,waste; 424 | STBRP_ASSERT(xpos >= 0); 425 | // find the left position that matches this 426 | while (node->next->x <= xpos) { 427 | prev = &node->next; 428 | node = node->next; 429 | } 430 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 431 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 432 | if (y + height <= c->height) { 433 | if (y <= best_y) { 434 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 435 | best_x = xpos; 436 | STBRP_ASSERT(y <= best_y); 437 | best_y = y; 438 | best_waste = waste; 439 | best = prev; 440 | } 441 | } 442 | } 443 | tail = tail->next; 444 | } 445 | } 446 | 447 | fr.prev_link = best; 448 | fr.x = best_x; 449 | fr.y = best_y; 450 | return fr; 451 | } 452 | 453 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 454 | { 455 | // find best position according to heuristic 456 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 457 | stbrp_node *node, *cur; 458 | 459 | // bail if: 460 | // 1. it failed 461 | // 2. the best node doesn't fit (we don't always check this) 462 | // 3. we're out of memory 463 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 464 | res.prev_link = NULL; 465 | return res; 466 | } 467 | 468 | // on success, create new node 469 | node = context->free_head; 470 | node->x = (stbrp_coord) res.x; 471 | node->y = (stbrp_coord) (res.y + height); 472 | 473 | context->free_head = node->next; 474 | 475 | // insert the new node into the right starting point, and 476 | // let 'cur' point to the remaining nodes needing to be 477 | // stiched back in 478 | 479 | cur = *res.prev_link; 480 | if (cur->x < res.x) { 481 | // preserve the existing one, so start testing with the next one 482 | stbrp_node *next = cur->next; 483 | cur->next = node; 484 | cur = next; 485 | } else { 486 | *res.prev_link = node; 487 | } 488 | 489 | // from here, traverse cur and free the nodes, until we get to one 490 | // that shouldn't be freed 491 | while (cur->next && cur->next->x <= res.x + width) { 492 | stbrp_node *next = cur->next; 493 | // move the current node to the free list 494 | cur->next = context->free_head; 495 | context->free_head = cur; 496 | cur = next; 497 | } 498 | 499 | // stitch the list back in 500 | node->next = cur; 501 | 502 | if (cur->x < res.x + width) 503 | cur->x = (stbrp_coord) (res.x + width); 504 | 505 | #ifdef _DEBUG 506 | cur = context->active_head; 507 | while (cur->x < context->width) { 508 | STBRP_ASSERT(cur->x < cur->next->x); 509 | cur = cur->next; 510 | } 511 | STBRP_ASSERT(cur->next == NULL); 512 | 513 | { 514 | int count=0; 515 | cur = context->active_head; 516 | while (cur) { 517 | cur = cur->next; 518 | ++count; 519 | } 520 | cur = context->free_head; 521 | while (cur) { 522 | cur = cur->next; 523 | ++count; 524 | } 525 | STBRP_ASSERT(count == context->num_nodes+2); 526 | } 527 | #endif 528 | 529 | return res; 530 | } 531 | 532 | // [DEAR IMGUI] Added STBRP__CDECL 533 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 534 | { 535 | const stbrp_rect *p = (const stbrp_rect *) a; 536 | const stbrp_rect *q = (const stbrp_rect *) b; 537 | if (p->h > q->h) 538 | return -1; 539 | if (p->h < q->h) 540 | return 1; 541 | return (p->w > q->w) ? -1 : (p->w < q->w); 542 | } 543 | 544 | // [DEAR IMGUI] Added STBRP__CDECL 545 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 546 | { 547 | const stbrp_rect *p = (const stbrp_rect *) a; 548 | const stbrp_rect *q = (const stbrp_rect *) b; 549 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 550 | } 551 | 552 | #ifdef STBRP_LARGE_RECTS 553 | #define STBRP__MAXVAL 0xffffffff 554 | #else 555 | #define STBRP__MAXVAL 0xffff 556 | #endif 557 | 558 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 559 | { 560 | int i, all_rects_packed = 1; 561 | 562 | // we use the 'was_packed' field internally to allow sorting/unsorting 563 | for (i=0; i < num_rects; ++i) { 564 | rects[i].was_packed = i; 565 | } 566 | 567 | // sort according to heuristic 568 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 569 | 570 | for (i=0; i < num_rects; ++i) { 571 | if (rects[i].w == 0 || rects[i].h == 0) { 572 | rects[i].x = rects[i].y = 0; // empty rect needs no space 573 | } else { 574 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 575 | if (fr.prev_link) { 576 | rects[i].x = (stbrp_coord) fr.x; 577 | rects[i].y = (stbrp_coord) fr.y; 578 | } else { 579 | rects[i].x = rects[i].y = STBRP__MAXVAL; 580 | } 581 | } 582 | } 583 | 584 | // unsort 585 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 586 | 587 | // set was_packed flags and all_rects_packed status 588 | for (i=0; i < num_rects; ++i) { 589 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 590 | if (!rects[i].was_packed) 591 | all_rects_packed = 0; 592 | } 593 | 594 | // return the all_rects_packed status 595 | return all_rects_packed; 596 | } 597 | #endif 598 | 599 | /* 600 | ------------------------------------------------------------------------------ 601 | This software is available under 2 licenses -- choose whichever you prefer. 602 | ------------------------------------------------------------------------------ 603 | ALTERNATIVE A - MIT License 604 | Copyright (c) 2017 Sean Barrett 605 | Permission is hereby granted, free of charge, to any person obtaining a copy of 606 | this software and associated documentation files (the "Software"), to deal in 607 | the Software without restriction, including without limitation the rights to 608 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 609 | of the Software, and to permit persons to whom the Software is furnished to do 610 | so, subject to the following conditions: 611 | The above copyright notice and this permission notice shall be included in all 612 | copies or substantial portions of the Software. 613 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 614 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 615 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 616 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 617 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 618 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 619 | SOFTWARE. 620 | ------------------------------------------------------------------------------ 621 | ALTERNATIVE B - Public Domain (www.unlicense.org) 622 | This is free and unencumbered software released into the public domain. 623 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 624 | software, either in source code form or as a compiled binary, for any purpose, 625 | commercial or non-commercial, and by any means. 626 | In jurisdictions that recognize copyright laws, the author or authors of this 627 | software dedicate any and all copyright interest in the software to the public 628 | domain. We make this dedication for the benefit of the public at large and to 629 | the detriment of our heirs and successors. We intend this dedication to be an 630 | overt act of relinquishment in perpetuity of all present and future rights to 631 | this software under copyright law. 632 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 633 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 634 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 635 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 636 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 637 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 638 | ------------------------------------------------------------------------------ 639 | */ 640 | -------------------------------------------------------------------------------- /3rdparty/stb/stb_image.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "stb_image.h" 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.3 ) 2 | 3 | if(NOT CMAKE_BUILD_TYPE) 4 | set(CMAKE_BUILD_TYPE Release) 5 | endif() 6 | if (POLICY CMP0048) 7 | cmake_policy(SET CMP0048 NEW) 8 | endif (POLICY CMP0048) 9 | if (POLICY CMP0069) 10 | cmake_policy(SET CMP0069 NEW) 11 | endif (POLICY CMP0069) 12 | if (POLICY CMP0072) 13 | cmake_policy(SET CMP0072 NEW) 14 | endif (POLICY CMP0072) 15 | 16 | project( meshview ) 17 | set(MESHVIEW_VERSION_MAJOR 0) 18 | set(MESHVIEW_VERSION_MINOR 0) 19 | set(MESHVIEW_VERSION_PATCH 1) 20 | set(MESHVIEW_VERSION ${MESHVIEW_VERSION_MAJOR}.${MESHVIEW_VERSION_MINOR}.${MESHVIEW_VERSION_PATCH}) 21 | 22 | option( MESHVIEW_USE_SYSTEM_EIGEN 23 | "Use system Eigen. If false, please set EIGEN_INCLUDE_DIR manually" ON ) 24 | option( MESHVIEW_USE_SYSTEM_GLFW 25 | "Use system glfw rather than the included glfw submodule if available" OFF ) 26 | option( MESHVIEW_BUILD_IMGUI "Build with Dear ImGui integrated GUI" ON ) 27 | option( MESHVIEW_BUILD_EXAMPLE "Build the example program" ON ) 28 | option( MESHVIEW_BUILD_INSTALL "Build the install target" ON ) 29 | option( MESHVIEW_BUILD_PYTHON "Build Python bindings" OFF ) 30 | option( MESHVIEW_USE_FFAST_MATH "Use -ffast-math" OFF ) 31 | 32 | set( CMAKE_CXX_STACK_SIZE "10000000" ) 33 | set( CMAKE_CXX_STANDARD 14 ) 34 | set( CMAKE_CXX_STANDARD_REQUIRED ON) 35 | set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ) 36 | 37 | set( INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" ) 38 | set( SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src" ) 39 | set( VENDOR_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty" ) 40 | set( STB_DIR "${VENDOR_DIR}/stb" ) 41 | set( IMGUI_DIR "${VENDOR_DIR}/imgui_mod" ) 42 | set( GLFW_DIR "${VENDOR_DIR}/glfw" ) 43 | set( GLEW_DIR "${VENDOR_DIR}/glew" ) 44 | 45 | set( PROJ_LIB_NAME "meshview" ) 46 | 47 | if ( CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang") ) 48 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -Wno-deprecated-declarations -O3 -funroll-loops -g" ) 49 | if( MESHVIEW_USE_FFAST_MATH ) 50 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffast-math" ) 51 | endif() 52 | elseif( MSVC ) 53 | if( MESHVIEW_USE_FFAST_MATH ) 54 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:fast" ) 55 | endif() 56 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") 57 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT /GLT /Ox") 58 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") 59 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT /GLT") 60 | # Silence deprecation warning coming from Eigen; silence secure warning 61 | add_definitions(-D_SILENCE_CXX17_RESULT_OF_DEPRECATION_WARNING -D_CRT_SECURE_NO_WARNINGS) 62 | endif ( ) 63 | 64 | # Git submodule auto update 65 | # https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html 66 | find_package(Git QUIET) 67 | if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") 68 | # Update submodules as needed 69 | option(GIT_SUBMODULE "Check submodules during build" ON) 70 | if(GIT_SUBMODULE) 71 | message(STATUS "Running git submodule update --init --recursive") 72 | execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive 73 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 74 | RESULT_VARIABLE GIT_SUBMOD_RESULT) 75 | if(NOT GIT_SUBMOD_RESULT EQUAL "0") 76 | message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") 77 | endif() 78 | message(STATUS "Submodule update done") 79 | endif() 80 | endif() 81 | 82 | if(NOT EXISTS "${GLFW_DIR}/CMakeLists.txt") 83 | message(FATAL_ERROR "A submodule as not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") 84 | endif() 85 | 86 | if ( MESHVIEW_BUILD_IMGUI ) 87 | message(STATUS "Building with Dear ImGui integrated GUI") 88 | set (_MESHVIEW_IMGUI_ "") 89 | file(GLOB IMGUI_SOURCES ${IMGUI_DIR}/meshview/imgui/*.cpp) 90 | set(IMGUI_HEADERS 91 | ${IMGUI_DIR}/meshview/imgui/imconfig.h 92 | ${IMGUI_DIR}/meshview/imgui/imgui.h 93 | ${IMGUI_DIR}/meshview/imgui/imgui_impl_glfw.h 94 | ${IMGUI_DIR}/meshview/imgui/imgui_impl_opengl3.h 95 | ${IMGUI_DIR}/meshview/imgui/imgui_stdlib.h 96 | ${IMGUI_DIR}/meshview/imgui/imstb_rectpack.h 97 | ${IMGUI_DIR}/meshview/imgui/imstb_textedit.h 98 | ${IMGUI_DIR}/meshview/imgui/imstb_truetype.h 99 | ) 100 | else() 101 | message(STATUS "Dear ImGui integrated GUI disabled") 102 | set (_MESHVIEW_IMGUI_ "//") 103 | set( IMGUI_SOURCES ) 104 | set (IMGUI_HEADERS ) 105 | endif() 106 | 107 | add_definitions(-DGLEW_STATIC) 108 | 109 | file(GLOB MESHVIEW_SOURCES ${SRC_DIR}/*.cpp) 110 | file(GLOB MESHVIEW_PUBLIC_HEADERS ${INCLUDE_DIR}/meshview/*.hpp) 111 | file(GLOB MESHVIEW_PRIVATE_HEADERS ${INCLUDE_DIR}/meshview/internal/*.hpp) 112 | 113 | # create meshview.hpp 114 | set ( COMMON_HPP_PATH "${PROJECT_BINARY_DIR}/include/meshview/common.hpp" ) 115 | configure_file( common.hpp.in "${COMMON_HPP_PATH}" ) 116 | set ( MESHVIEW_PUBLIC_HEADERS ${MESHVIEW_PUBLIC_HEADERS} "${COMMON_HPP_PATH}") 117 | 118 | set ( MESHVIEW_HEADERS ${MESHVIEW_PUBLIC_HEADERS} ${MESHVIEW_PRIVATE_HEADERS} ) 119 | 120 | set( MESHVIEW_VENDOR_SOURCES 121 | ${STB_DIR}/stb_image.cpp 122 | ${GLEW_DIR}/src/glew.c 123 | ) 124 | 125 | source_group( "Header Files" FILES ${MESHVIEW_HEADERS} ) 126 | source_group( "Source Files" FILES ${MESHVIEW_SOURCES} ) 127 | 128 | set( DEPENDENCIES ) 129 | 130 | if ( MESHVIEW_USE_SYSTEM_EIGEN ) 131 | find_package(Eigen3 3.3 REQUIRED NO_MODULE) 132 | if (TARGET Eigen3::Eigen) 133 | message(STATUS "Using system Eigen") 134 | set (DEPENDENCIES ${DEPENDENCIES} Eigen3::Eigen) 135 | else() 136 | message(STATUS "System Eigen > 3.3 not found, using vendor Eigen") 137 | include_directories(${EIGEN_DIR}) 138 | endif() 139 | else() 140 | # Set EIGEN_INCLUDE_DIR manually 141 | include_directories(${EIGEN_INCLUDE_DIR}) 142 | endif() 143 | 144 | include_directories( 145 | ${STB_DIR} 146 | ${GLEW_DIR}/include 147 | ) 148 | 149 | find_package(OpenGL REQUIRED) 150 | set( DEPENDENCIES ${DEPENDENCIES} OpenGL::GL ) 151 | 152 | set ( WILL_USE_SYSTEM_GLFW ${MESHVIEW_USE_SYSTEM_GLFW} ) 153 | 154 | find_package(PkgConfig) 155 | if ( NOT PkgConfig_FOUND ) 156 | set ( WILL_USE_SYSTEM_GLFW OFF ) 157 | else() 158 | pkg_check_modules(GLFW glfw3) 159 | if ( NOT GLFW_FOUND ) 160 | set ( WILL_USE_SYSTEM_GLFW OFF ) 161 | endif () 162 | endif () 163 | 164 | if ( NOT WILL_USE_SYSTEM_GLFW ) 165 | message ( STATUS "Using included glfw3 (in 3rdparty/)" ) 166 | # Build GLFW 167 | SET(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "GLFW example" FORCE) 168 | SET(GLFW_BUILD_TESTS OFF CACHE BOOL "GLFW tests" FORCE) 169 | SET(GLFW_BUILD_DOCS OFF CACHE BOOL "GLFW docs" FORCE) 170 | SET(GLFW_INSTALL ${MESHVIEW_BUILD_INSTALL} CACHE BOOL "GLFW install" FORCE) 171 | add_subdirectory(${GLFW_DIR}) 172 | set( DEPENDENCIES ${DEPENDENCIES} glfw ) 173 | include_directories( "${GLFW_DIR}/include" ) 174 | else() 175 | message ( STATUS "Using system glfw3" ) 176 | set( DEPENDENCIES ${DEPENDENCIES} glfw3 ) 177 | endif() 178 | 179 | find_package(Threads REQUIRED) 180 | 181 | # Optional pybind11 182 | find_package( pybind11 ) 183 | if (NOT pybind11_FOUND AND ${MESHVIEW_BUILD_PYTHON}) 184 | message( WARNING "pybind11 not found, Python binding is disabled. Install from https://github.com/pybind/pybind11" ) 185 | endif() 186 | 187 | add_library( ${PROJ_LIB_NAME} STATIC ${MESHVIEW_SOURCES} 188 | ${IMGUI_SOURCES} ${MESHVIEW_VENDOR_SOURCES} ) 189 | 190 | target_include_directories( 191 | ${PROJ_LIB_NAME} PUBLIC 192 | $ 193 | $ 194 | $ 195 | ) 196 | if (MESHVIEW_BUILD_IMGUI) 197 | target_include_directories( 198 | ${PROJ_LIB_NAME} PUBLIC 199 | $ 200 | ) 201 | endif() 202 | target_link_libraries( ${PROJ_LIB_NAME} PUBLIC 203 | ${DEPENDENCIES} 204 | ${CMAKE_THREAD_LIBS_INIT} 205 | ${CMAKE_DL_LIBS} 206 | ) 207 | 208 | if (MESHVIEW_BUILD_INSTALL) 209 | if ( NOT WILL_USE_SYSTEM_GLFW ) 210 | # Ugly hack 211 | export(TARGETS glfw 212 | NAMESPACE 213 | FILE "${CMAKE_CURRENT_BINARY_DIR}/3rdparty/glfwConfig.cmake") 214 | endif() 215 | set_target_properties( ${PROJ_LIB_NAME} PROPERTIES 216 | PUBLIC_HEADER "${MESHVIEW_PUBLIC_HEADERS}" 217 | ) 218 | 219 | include(GNUInstallDirs) 220 | install(TARGETS ${PROJ_LIB_NAME} 221 | EXPORT ${PROJ_LIB_NAME}Targets 222 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 223 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/meshview 224 | ) 225 | export(TARGETS ${PROJ_LIB_NAME} 226 | NAMESPACE meshview:: 227 | FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJ_LIB_NAME}Config.cmake") 228 | install(EXPORT 229 | ${PROJ_LIB_NAME}Targets 230 | DESTINATION "${CMAKE_INSTALL_DATADIR}/meshview/cmake" 231 | NAMESPACE meshview:: 232 | ) 233 | install(FILES "${SRC_DIR}/cmake/meshviewConfig.cmake" 234 | DESTINATION "${CMAKE_INSTALL_DATADIR}/meshview/cmake") 235 | if (MESHVIEW_BUILD_IMGUI) 236 | install(FILES ${IMGUI_HEADERS} 237 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/meshview/imgui") 238 | endif() 239 | endif(MESHVIEW_BUILD_INSTALL) 240 | 241 | if (MESHVIEW_BUILD_EXAMPLE) 242 | add_executable( example example.cpp ) 243 | target_link_libraries( example ${PROJ_LIB_NAME} ) 244 | set_target_properties( example PROPERTIES OUTPUT_NAME "meshview-example" ) 245 | endif() 246 | 247 | if (${pybind11_FOUND} AND ${MESHVIEW_BUILD_PYTHON}) 248 | message(STATUS "Building Python bindings") 249 | pybind11_add_module(pymeshview SHARED ${MESHVIEW_SOURCES} ${IMGUI_SOURCES} ${MESHVIEW_VENDOR_SOURCES} pybind.cpp) 250 | set_target_properties(pymeshview PROPERTIES OUTPUT_NAME meshview) 251 | target_include_directories( 252 | pymeshview PUBLIC 253 | $ 254 | $ 255 | $ 256 | ) 257 | if (MESHVIEW_BUILD_IMGUI) 258 | target_include_directories( 259 | pymeshview PUBLIC 260 | $ 261 | ) 262 | endif() 263 | target_link_libraries(pymeshview PRIVATE ${DEPENDENCIES} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) 264 | endif () 265 | 266 | if(WIN32) 267 | add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES) 268 | if (MSVC AND MESHVIEW_BUILD_EXAMPLE) 269 | set_property(TARGET example APPEND PROPERTY LINK_FLAGS "/DEBUG /LTCG" ) 270 | endif ( MSVC AND MESHVIEW_BUILD_EXAMPLE ) 271 | endif() 272 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### C++ mesh + point cloud visualizer 2 | A basic OpenGL visualizer for meshes and pointclouds. 3 | Uses Eigen matrices to store data, which is convenient for projects already using Eigen. 4 | 5 | Example of visualizer created using meshview: 6 | ![Screenshot of smplx-viewer](https://github.com/sxyu/meshview/blob/master/readme-img/smpl-visual.png?raw=true) 7 | Code: 8 | 9 | Azure Kinect camera point cloud visualization: 10 | ![Screenshot of pcview (depth map viewer)](https://github.com/sxyu/meshview/blob/master/readme-img/depth-cam.png?raw=true) 11 | 12 | Scene from `example.cpp`: 13 | ![Screenshot of example](https://github.com/sxyu/meshview/blob/master/readme-img/example.png?raw=true) 14 | 15 | ### Features 16 | - Drawing meshes (using phong lighting model) 17 | - Coloring mesh via texture mapping 18 | - Diffuse and specular textures 19 | - Alternatively, coloring mesh by interpolating vertex color 20 | - Automated vertex normal computation 21 | - Drawing point clouds 22 | - Drawing poly-lines (as point clouds with .line=true) 23 | - Drawing several geometric objects (line, cube, square, [UV] sphere) directly 24 | - Controlling camera and lighting 25 | - RGB/XYZ axes 26 | - Optionally includes Dear ImGUI which can be used without any additional setup, by 27 | writing ImGui calls (like ImGui::Begin()) in the `viewer.on_gui` event handler 28 | 29 | Note this project does not support importing/exporting models, and is 30 | mostly intended for visualizing programmatically generated objects. 31 | For model I/O please look into integrating assimp. 32 | 33 | ```cpp 34 | #include "meshview/meshview.hpp" 35 | ... 36 | 37 | meshview::Viewer viewer; 38 | // do drawing 39 | viewer.add_xyz(args); 40 | // handle events 41 | viewer.on_xyz = [&](args) { 42 | } 43 | viewer.show(); 44 | ``` 45 | 46 | See `example.cpp` for usage examples. 47 | 48 | ## Installation 49 | ### External dependencies 50 | - OpenGL 3+ 51 | - C++14 52 | - Eigen 3.3 or 3.4 beta 53 | 54 | ### Build 55 | Use CMake in the usual way. 56 | `mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release` 57 | 58 | - Unix: `make -j && make install` 59 | - Windows: `cmake --build . --target Release` 60 | 61 | Options: 62 | - `-DMESHVIEW_BUILD_IMGUI=OFF` to disable Dear ImGui GUI system 63 | - `-DMESHVIEW_BUILD_EXAMPLE=OFF` to disable building the (very simple) example program 64 | -------------------------------------------------------------------------------- /common.hpp.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MESHVIEW_COMMON_93D99C8D_E8CA_4FFE_9716_D8237925F910 3 | #define MESHVIEW_COMMON_93D99C8D_E8CA_4FFE_9716_D8237925F910 4 | 5 | #include 6 | 7 | @_MESHVIEW_IMGUI_@#ifndef MESHVIEW_IMGUI 8 | @_MESHVIEW_IMGUI_@#define MESHVIEW_IMGUI 9 | @_MESHVIEW_IMGUI_@#endif 10 | #define MESHVIEW_VERSION_MAJOR @MESHVIEW_VERSION_MAJOR@ 11 | #define MESHVIEW_VERSION_MINOR @MESHVIEW_VERSION_MINOR@ 12 | #define MESHVIEW_VERSION_PATCH @MESHVIEW_VERSION_PATCH@ 13 | 14 | namespace meshview { 15 | 16 | using Matrix2f = Eigen::Matrix2f; 17 | using Matrix3f = Eigen::Matrix3f; 18 | using Matrix4f = Eigen::Matrix4f; 19 | using Vector2f = Eigen::Vector2f; 20 | using Vector3f = Eigen::Vector3f; 21 | using Vector4f = Eigen::Vector4f; 22 | 23 | using Index = uint32_t; 24 | using Points = Eigen::Matrix; 25 | using PointsRGBNormal = Eigen::Matrix; 26 | using PointsRGB = Eigen::Matrix; 27 | using Points2D = Eigen::Matrix; 28 | 29 | using Matrix = Eigen::Matrix; 30 | using Vector = Eigen::Matrix; 31 | 32 | using Triangles = Eigen::Matrix; 33 | 34 | using Image = Eigen::Matrix; 35 | using ImageU = Eigen::Matrix; 36 | 37 | namespace input { 38 | // Key/button action 39 | enum class Action { 40 | release, press, repeat 41 | }; 42 | } // namespace input 43 | 44 | } 45 | 46 | #endif // ifndef MESHVIEW_COMMON_93D99C8D_E8CA_4FFE_9716_D8237925F910 47 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/meshview.hpp" 2 | #include "meshview/meshview_imgui.hpp" 3 | #include "Eigen/Core" 4 | #include "Eigen/Geometry" 5 | #include 6 | 7 | using namespace meshview; 8 | 9 | int main(int argc, char** argv) { 10 | Viewer viewer; 11 | viewer.draw_axes = true; 12 | // Adjust camera 13 | viewer.camera.dist_to_center = 5.f; 14 | // viewer.camera.center_of_rot = Vector3f(); 15 | 16 | // Here's how to adjust lighting 17 | // viewer.light_pos = Vector3f() 18 | // viewer.light_color_ambient = Vector3f(); 19 | // viewer.light_color_diffuse = Vector3f(); 20 | // viewer.light_color_specular = Vector3f(); 21 | 22 | // ** Some primitives 23 | // * Draw line 24 | auto& line = viewer.add_line(Vector3f(-1.f, 1.f, 1.f), Vector3f::Ones(), 25 | /*color*/ Vector3f(1.f, 1.f, 0.f)); 26 | 27 | // * Textured cube 28 | Image mat(256, 256 * 3 /*channels*/); 29 | Image mat_spec(256, 256 * 3 /*channels*/); 30 | for (int i = 0; i < 256; ++i) { 31 | for (int j = 0; j < 256; ++j) { 32 | // Manually generate texture image 33 | mat.block<1, 3>(i, j * 3) << i / 255.f, // r 34 | 1.f - i / 255.f, // g 35 | mat(i, j * 3) = 0.5f; // b 36 | mat_spec.block<1, 3>(i, j * 3).setConstant(1.f - 37 | (j - 128.f) / 128.f); 38 | } 39 | } 40 | auto& cube = 41 | viewer.add_cube() 42 | .translate(Vector3f(-2.f, 0.f, 0.f)) 43 | // Diffuse texture (optional) 44 | .add_texture(mat, /* channels */ 3) 45 | // Specular texture (optional) 46 | .add_texture(mat_spec, /* channels */ 3); 47 | 48 | // * Basic sphere 49 | auto& uv_sph = 50 | viewer 51 | .add_sphere(Vector3f::Zero(), 52 | /* radius */ 0.5f) // use defalt color orange 53 | .translate(Vector3f(2.f, 0.f, 0.f)) 54 | .set_shininess(32.f); 55 | 56 | // * Point Cloud: random 57 | Points random_pts(150, 3); 58 | random_pts.setRandom(); 59 | viewer.add_point_cloud(random_pts, /*r,g,b*/ 0.f, 1.0f, 1.0f) 60 | .rotate(Eigen::AngleAxisf(-M_PI / 2.f, Eigen::Vector3f(1.f, 0.f, 0.f)) 61 | .toRotationMatrix()) 62 | .scale(1.5f); 63 | 64 | // * Triangle mesh: single color 65 | Points pyra_vert(3 * 6, 3); 66 | pyra_vert << -1.f, -1.f, -1.f, -1.f, 1.f, -1.f, 1.f, -1.f, -1.f, 67 | 68 | -1.f, 1.f, -1.f, 1.f, 1.f, -1.f, 1.f, -1.f, -1.f, 69 | 70 | -1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, 1.f, -1.f, 71 | 72 | -1.f, 1.f, -1.f, 0.f, 0.f, 1.f, 1.f, 1.f, -1.f, 73 | 74 | 1.f, 1.f, -1.f, 0.f, 0.f, 1.f, 1.f, -1.f, -1.f, 75 | 76 | 1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, -1.f, -1.f; 77 | auto& pyra = viewer 78 | .add_mesh(pyra_vert, 79 | /* Triangle indices (unsigned fx3) here; pass 80 | empty to use: 0 1 2, 3 4 5 etc */ 81 | Triangles(), 82 | /*r,g,b*/ 0.f, 1.0f, 1.0f) 83 | .translate(Vector3f(0.f, 0.f, 3.f)) 84 | .set_shininess(32.f); 85 | 86 | // * Triangle mesh: vertex-interpolated colors 87 | Points pyra_vert_colors(3 * 6, 3); 88 | pyra_vert_colors << 1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1.f, 0.f, 0.f, 89 | 90 | 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, 0.f, 0.f, 91 | 92 | 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1.f, 0.f, 93 | 94 | 0.f, 1.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f, 95 | 96 | 1.f, 1.f, 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 0.f, 97 | 98 | 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 0.f; 99 | auto& pyra2 = viewer 100 | .add_mesh(pyra_vert, 101 | /* Triangle indices (unsigned fx3) here; pass 102 | empty to use: 0 1 2, 3 4 5 etc */ 103 | Triangles(), 104 | /* vx3 color matrix*/ pyra_vert_colors) 105 | .set_shininess(32.f) 106 | .translate(Vector3f(3.f, 3.f, 0.f)); 107 | 108 | // * Triangle mesh: textured (tex coords are for demonstration only, not a 109 | // real uv unwrap) 110 | Points2D pyra_vert_texcoord(3 * 6, 2); 111 | pyra_vert_texcoord << 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 112 | 113 | 0.f, 1.f, 1.f, 1.f, 1.f, 0.f, 114 | 115 | 0.f, 0.f, 0.5f, 0.5f, 0.f, 1.f, 116 | 117 | 0.f, 1.f, 0.5f, 0.5f, 1.f, 1.f, 118 | 119 | 1.f, 1.f, 0.5f, 0.5f, 1.f, 0.f, 120 | 121 | 1.f, 0.f, 0.5f, 0.5f, 0.f, 0.f; 122 | Triangles pyra_vert_tex_triangles(6, 3); 123 | // these indices are tex coord indices corresponding to the vertex 124 | // indices of each face 125 | for (size_t i = 0; i < pyra_vert_tex_triangles.size(); ++i) 126 | pyra_vert_tex_triangles.data()[i] = i; 127 | 128 | auto& pyra3 = 129 | viewer 130 | .add_mesh(pyra_vert, 131 | /* Triangle indices (unsigned fx3) here; pass empty to 132 | use: 0 1 2, 3 4 5 etc */ 133 | Triangles()) 134 | .set_tex_coords(pyra_vert_texcoord, pyra_vert_tex_triangles) 135 | .translate(Vector3f(-3.f, 3.f, 0.f)) 136 | // Diffuse texture (reusing from above) 137 | .add_texture(mat, /* channels */ 3) 138 | // Specular texture (reusing from above) 139 | .add_texture(mat_spec, /* channels */ 3); 140 | 141 | // * Events: key handler 142 | viewer.on_key = [&](int button, input::Action action, int mods) -> bool { 143 | if (action != input::Action::release) { 144 | if (button == 'D') { 145 | // Press D to make textured pyramid go right 146 | pyra3.translate(Vector3f(0.05f, 0.f, 0.f)); 147 | } else if (button == 'E') { 148 | // Press E to make textured pyramid's top vertex become larger 149 | pyra3.verts_pos()(4, 2) += 0.1f; 150 | // In order to update mesh, we need to use: 151 | pyra3.update(); 152 | // OR if we are in on_loop or on_gui event we can return true 153 | // to update all 154 | } 155 | } 156 | return true; // Don't prevent default 157 | }; 158 | // * Events: loop callback 159 | viewer.on_loop = [&]() -> bool { 160 | return false; // True to update all meshes and camera 161 | }; 162 | #ifdef MESHVIEW_IMGUI 163 | viewer.on_gui = [&]() -> bool { 164 | ImGui::SetNextWindowSize(ImVec2(200, 100)); 165 | ImGui::Begin("Hello"); 166 | ImGui::Text("Hello world"); 167 | ImGui::Button("Panic button"); 168 | ImGui::End(); 169 | return false; // True to update all meshes and camera 170 | }; 171 | #else 172 | std::cout 173 | << "meshview was built without Dear ImGUI, no GUI will be available\n"; 174 | #endif 175 | viewer.show(); 176 | } 177 | -------------------------------------------------------------------------------- /include/meshview/internal/assert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define _MESHVIEW_ASSERT(x) do { \ 4 | if (!(x)) { \ 5 | std::cerr << "meshview assertion FAILED: \"" << #x << "\" (" << (bool)(x) << \ 6 | ")\n at " << __FILE__ << " line " << __LINE__ <<"\n"; \ 7 | std::exit(1); \ 8 | }} while(0) 9 | #define _MESHVIEW_ASSERT_EQ(x, y) do {\ 10 | if ((x) != (y)) { \ 11 | std::cerr << "meshview assertion FAILED: " << #x << \ 12 | " == " << #y << " (" << (x) << " != " << (y) << \ 13 | ")\n at " << __FILE__ << " line " << __LINE__ <<"\n"; \ 14 | std::exit(1); \ 15 | }} while(0) 16 | #define _MESHVIEW_ASSERT_NE(x, y) do {\ 17 | if ((x) == (y)) { \ 18 | std::cerr << "meshview assertion FAILED: " << #x << \ 19 | " != " << #y << " (" << (x) << " == " << (y) << \ 20 | ")\n at " << __FILE__ << " line " << __LINE__ <<"\n"; \ 21 | std::exit(1); \ 22 | }} while(0) 23 | #define _MESHVIEW_ASSERT_LE(x, y) do {\ 24 | if ((x) > (y)) { \ 25 | std::cerr << "meshview assertion FAILED: " << #x << \ 26 | " <= " << #y << " (" << (x) << " > " << (y) << \ 27 | ")\n at " << __FILE__ << " line " << __LINE__ <<"\n"; \ 28 | std::exit(1); \ 29 | }} while(0) 30 | #define _MESHVIEW_ASSERT_LT(x, y) do {\ 31 | if ((x) >= (y)) { \ 32 | std::cerr << "meshview assertion FAILED: " << #x << \ 33 | " < " << #y << " (" << (x) << " >= " << (y) << \ 34 | ")\n at " << __FILE__ << " line " << __LINE__ <<"\n"; \ 35 | std::exit(1); \ 36 | }} while(0) 37 | -------------------------------------------------------------------------------- /include/meshview/internal/shader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MESHVIEW_SHADER_9845A71E_0422_44A7_9AF9_FAC46ECE9C40 3 | #define MESHVIEW_SHADER_9845A71E_0422_44A7_9AF9_FAC46ECE9C40 4 | 5 | #include 6 | #include "meshview/common.hpp" 7 | 8 | namespace meshview { 9 | namespace internal { 10 | 11 | class Shader { 12 | public: 13 | // Load existing shader from id 14 | explicit Shader(Index id); 15 | // Load shader on construction from code 16 | Shader(const std::string& vertex_code, 17 | const std::string& fragment_code, 18 | const std::string& geometry_code = ""); 19 | // Activate the shader 20 | void use(); 21 | 22 | // Utility uniform functions 23 | void set_bool(const std::string &name, bool value) const; 24 | void set_int(const std::string &name, int value) const; 25 | void set_float(const std::string &name, float value) const; 26 | void set_vec2(const std::string &name, float x, float y) const; 27 | void set_vec3(const std::string &name, float x, float y, float z) const; 28 | void set_vec4(const std::string &name, float x, float y, float z, float w); 29 | 30 | // Eigen helpers 31 | void set_vec2(const std::string &name, const Eigen::Ref& value) const; 32 | void set_vec3(const std::string &name, const Eigen::Ref& value) const; 33 | void set_vec4(const std::string &name, const Eigen::Ref& value) const; 34 | void set_mat2(const std::string &name, const Eigen::Ref &mat) const; 35 | void set_mat3(const std::string &name, const Eigen::Ref &mat) const; 36 | void set_mat4(const std::string &name, const Eigen::Ref &mat) const; 37 | 38 | // GL shader id 39 | Index id; 40 | }; 41 | 42 | } // namespace internal 43 | } // namespace meshview 44 | 45 | #endif // ifndef MESHVIEW_SHADER_9845A71E_0422_44A7_9AF9_FAC46ECE9C40 46 | -------------------------------------------------------------------------------- /include/meshview/internal/shader_inline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef VIEWER_SHADER_INLINE_91D27C05_59C0_4F9F_A6C5_6AA9E2000CDA 3 | #define VIEWER_SHADER_INLINE_91D27C05_59C0_4F9F_A6C5_6AA9E2000CDA 4 | 5 | namespace meshview { 6 | 7 | // Shading for mesh using texture 8 | static const char* MESH_VERTEX_SHADER = R"SHADER( 9 | #version 330 core 10 | layout(location = 0) in vec3 aPosition; 11 | layout(location = 1) in vec3 aTexCoord; 12 | layout(location = 2) in vec3 aNormal; 13 | 14 | out vec3 FragPos; 15 | out vec2 TexCoord; 16 | out vec3 Normal; 17 | 18 | uniform mat4 M; 19 | uniform mat4 MVP; 20 | uniform mat3 NormalMatrix; 21 | 22 | void main() { 23 | TexCoord = aTexCoord.xy; 24 | FragPos = (M * vec4(aPosition, 1.0f)).xyz; 25 | Normal = NormalMatrix * aNormal; 26 | gl_Position = MVP * vec4(aPosition, 1.0f); 27 | })SHADER"; 28 | 29 | static const char* MESH_FRAGMENT_SHADER = R"SHADER( 30 | #version 330 core 31 | out vec4 FragColor; 32 | struct Material { 33 | sampler2D diffuse; 34 | sampler2D specular; 35 | float shininess; 36 | }; 37 | struct Light { 38 | vec3 position; 39 | vec3 ambient; 40 | vec3 diffuse; 41 | vec3 specular; 42 | }; 43 | 44 | in vec3 FragPos; // Position (world) 45 | in vec2 TexCoord; // UV coords 46 | in vec3 Normal; // Normal vector (world) 47 | uniform vec3 viewPos; // Camera position (world) 48 | uniform Material material; // Material info 49 | uniform Light light; // Light info 50 | 51 | void main(){ 52 | vec3 objectColor = texture(material.diffuse, TexCoord).rgb; 53 | vec3 ambient = light.ambient * objectColor; 54 | 55 | vec3 norm = normalize(Normal); 56 | vec3 lightDir = normalize(light.position - FragPos); 57 | float diff = max(dot(norm, lightDir), 0.0f); 58 | vec3 diffuse = light.diffuse * diff * objectColor; 59 | 60 | vec3 viewDir = normalize(viewPos - FragPos); 61 | vec3 halfwayDir = normalize(lightDir + viewDir); 62 | float spec = pow(max(dot(viewDir, halfwayDir), 0.0), material.shininess); 63 | vec3 specular = light.specular * spec * texture(material.specular, TexCoord).rgb; 64 | 65 | FragColor = vec4(ambient + diffuse + specular, 1.0f); 66 | })SHADER"; 67 | 68 | // Shading for mesh, using interpolated per-vertex colors instead of texture 69 | static const char* MESH_VERTEX_SHADER_VERT_COLOR = R"SHADER( 70 | #version 330 core 71 | layout(location = 0) in vec3 aPosition; 72 | layout(location = 1) in vec3 aVertColor; 73 | layout(location = 2) in vec3 aNormal; 74 | 75 | out vec3 FragPos; 76 | out vec3 VertColor; 77 | out vec3 Normal; 78 | 79 | uniform mat4 M; 80 | uniform mat4 MVP; 81 | uniform mat3 NormalMatrix; 82 | 83 | void main() { 84 | VertColor = aVertColor; 85 | FragPos = (M * vec4(aPosition, 1.0f)).xyz; 86 | Normal = NormalMatrix * aNormal; 87 | gl_Position = MVP * vec4(aPosition, 1.0f); 88 | })SHADER"; 89 | 90 | static const char* MESH_FRAGMENT_SHADER_VERT_COLOR = R"SHADER( 91 | #version 330 core 92 | out vec4 FragColor; 93 | struct Light { 94 | vec3 position; 95 | vec3 ambient; 96 | vec3 diffuse; 97 | vec3 specular; 98 | }; 99 | struct Material { 100 | float shininess; 101 | }; 102 | 103 | in vec3 FragPos; // Position (world) 104 | in vec3 VertColor; // Vertex color 105 | in vec3 Normal; // Normal vector (world) 106 | uniform vec3 viewPos; // Camera position (world) 107 | uniform Light light; // Light info 108 | uniform Material material; // Limited material info 109 | 110 | void main() { 111 | vec3 ambient = light.ambient * VertColor; 112 | 113 | vec3 norm = normalize(Normal); 114 | vec3 lightDir = normalize(light.position - FragPos); 115 | float diff = max(dot(norm, lightDir), 0.0f); 116 | vec3 diffuse = light.diffuse * diff * VertColor; 117 | 118 | vec3 viewDir = normalize(viewPos - FragPos); 119 | vec3 halfwayDir = normalize(lightDir + viewDir); 120 | float spec = pow(max(dot(viewDir, halfwayDir), 0.0), material.shininess); 121 | vec3 specular = light.specular * spec; 122 | 123 | FragColor = vec4(ambient + diffuse + specular, 1.0f); 124 | })SHADER"; 125 | 126 | // Very simple shading for point cloud (points and polylines) 127 | static const char* POINTCLOUD_VERTEX_SHADER = R"SHADER( 128 | #version 330 core 129 | layout(location = 0) in vec3 aPosition; 130 | layout(location = 1) in vec3 aColor; 131 | out vec3 Color; 132 | uniform mat4 M; 133 | uniform mat4 MVP; 134 | void main() { 135 | Color = aColor; 136 | gl_Position = MVP * vec4(aPosition, 1.0f); 137 | } 138 | )SHADER"; 139 | 140 | static const char* POINTCLOUD_FRAGMENT_SHADER = R"SHADER( 141 | #version 330 core 142 | 143 | out vec4 FragColor; // Ouput data 144 | in vec3 Color; // Color 145 | void main(){ 146 | // Finish 147 | FragColor = vec4(Color, 1.0f); 148 | } 149 | )SHADER"; 150 | 151 | } // namespace meshview 152 | 153 | #endif // ifndef VIEWER_SHADER_INLINE_91D27C05_59C0_4F9F_A6C5_6AA9E2000CDA 154 | -------------------------------------------------------------------------------- /include/meshview/meshview.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MESHVIEW_B1FE2D07_A12E_4C8B_A673_D9AC48841D24 3 | #define MESHVIEW_B1FE2D07_A12E_4C8B_A673_D9AC48841D24 4 | 5 | #include "meshview/common.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace meshview { 15 | 16 | // Represents a texture/material 17 | struct Texture { 18 | // Texture types 19 | enum { 20 | TYPE_DIFFUSE, 21 | TYPE_SPECULAR, 22 | // TYPE_NORMAL, 23 | // TYPE_HEIGHT, 24 | __TYPE_COUNT 25 | }; 26 | // Texture type names (TYPE_DIFFUSE -> "diffuse" etc) 27 | static constexpr inline const char* type_to_name(int type) { 28 | switch (type) { 29 | case TYPE_SPECULAR: 30 | return "specular"; 31 | // case TYPE_NORMAL: return "normal"; 32 | // case TYPE_HEIGHT: return "height"; 33 | default: 34 | return "diffuse"; 35 | } 36 | } 37 | 38 | // Texture from path 39 | Texture(const std::string& path, bool flip = true); 40 | // Texture from solid color (1x1) 41 | Texture(float r, float g, float b); 42 | // Texture from an (rows, cols*3) row-major 43 | // red(1-channel)/RGB(3-channel)/RGBA(4-channel) f32 matrix 44 | Texture(const Eigen::Ref& im, int n_channels = 1); 45 | 46 | ~Texture(); 47 | 48 | // Load texture; need to be called before first use in each context 49 | // (called by Mesh::reset()) 50 | void load(); 51 | 52 | // ADVANCED: free buffers, called by destructor 53 | void free_bufs(); 54 | 55 | // GL texture id; -1 if unavailable 56 | Index id = -1; 57 | 58 | private: 59 | // File path (optional) 60 | std::string path; 61 | 62 | // Image data (optional) 63 | Image im_data; 64 | // Channels in above 65 | int n_channels; 66 | 67 | // Color to use if path empty OR failed to load the texture image 68 | Vector3f fallback_color; 69 | 70 | // Vertical flip on load? 71 | bool flip; 72 | }; 73 | 74 | class Camera { 75 | public: 76 | // Construct camera with given params 77 | explicit Camera(const Vector3f& center_of_rot = Eigen::Vector3f(0.f, 0.f, 78 | 0.f), 79 | const Vector3f& world_up = Eigen::Vector3f(0.f, 1.f, 0.f), 80 | float dist_to_center = 3.f, float yaw = -M_PI / 2, 81 | float pitch = 0.0f, float roll = 0.0f, bool ortho = false, 82 | float fovy = M_PI / 4.f, float aspect = 5.f / 3.f, 83 | float z_close = 0.01f, float z_far = 1e3f); 84 | 85 | // Get camera position 86 | inline Vector3f get_pos() const { return pos; } 87 | 88 | // Update view matrix, call after changing any view parameter 89 | void update_view(); 90 | 91 | // Update proj matrix, call after changing any projection parameter 92 | void update_proj(); 93 | 94 | // Handlers 95 | void rotate_with_mouse(float xoffset, float yoffset); 96 | void roll_with_mouse(float xoffset, float yoffset); 97 | void pan_with_mouse(float xoffset, float yoffset); 98 | void zoom_with_mouse(float amount); 99 | 100 | // Reset the view 101 | void reset_view(); 102 | // Reset the projection 103 | void reset_proj(); 104 | 105 | // * Camera matrices 106 | // View matrix: global -> view coords 107 | Matrix4f view; 108 | // Projection matrix: view -> clip coords (perspective) 109 | Matrix4f proj; 110 | 111 | // Camera mouse control options 112 | float pan_speed = .0015f, rotate_speed = .008f, scroll_factor = 1.1f; 113 | 114 | // * Projection parameters 115 | // Orthographic proj? 116 | bool ortho; 117 | // Field of view aspect ratio 118 | float fovy, aspect; 119 | // Clip distances 120 | float z_close, z_far; 121 | 122 | // * View parameters 123 | // Center of rotation 124 | Vector3f center_of_rot; 125 | // Normalized direction vectors 126 | Vector3f front, up, world_up; 127 | // Distance to cor 128 | float dist_to_center; 129 | 130 | // Euler angles, for mouse control 131 | float yaw, pitch, roll; 132 | 133 | private: 134 | // For caching only; right = front cross up; pos = cor - d2c * front 135 | Vector3f pos, right; 136 | }; 137 | 138 | // Represents a triangle mesh with vertices (including uv, normals), 139 | // triangular faces, and textures 140 | class Mesh { 141 | public: 142 | enum class ShadingType { 143 | // Color by linear interpolation from vertices, use vertex color as rgb 144 | vertex, 145 | // Color by texture, use color info as texture uv coordinates 146 | texture 147 | }; 148 | 149 | // Construct uninitialized mesh with given number of vertices, triangles 150 | explicit Mesh(size_t num_verts = 0, size_t num_triangles = 0); 151 | // Construct from basic OBJ (see load_basic_obj) 152 | explicit Mesh(const std::string& path); 153 | // Construct with given points, faces, and (optionally) color data 154 | // faces: triangles. if not specified, assumes they are 0 1 2, 3 4 5 etc 155 | // rgb: optional per-vertex color (ignored if you call set_tex_coords later) 156 | // normals: normal vectors per point. 157 | // if not specified, will compute automatically on update 158 | explicit Mesh(const Eigen::Ref& pos, 159 | const Eigen::Ref& tri_faces, 160 | const Eigen::Ref& rgb, 161 | const Eigen::Ref& normals = Points()); 162 | // Construct with given points, faces, and (optionally) rgb color for all 163 | // vertices r,g,b: the color given to all vertices (ignored if you call 164 | // set_tex_coords later) normals: normal vectors per point. if not 165 | // specified, will compute automatically on update 166 | explicit Mesh(const Eigen::Ref& pos, 167 | const Eigen::Ref& tri_faces = Triangles(), 168 | float r = 1.f, float g = 1.f, float b = 1.f, 169 | const Eigen::Ref& normals = Points()); 170 | ~Mesh(); 171 | 172 | // Resize mesh (destroys current data) 173 | void resize(size_t num_verts, size_t num_triangles = 0); 174 | 175 | // Draw mesh to shader wrt camera 176 | void draw(Index shader_id, const Camera& camera); 177 | 178 | // Set the texture coordinates with given texture triangles 179 | // (shading mode will be set to texture) 180 | Mesh& set_tex_coords(const Eigen::Ref& coords, 181 | const Eigen::Ref& tri_faces); 182 | 183 | // Remove texture coordinates data 184 | // (shading mode will be set to vertex) 185 | Mesh& unset_tex_coords(); 186 | 187 | // Add a texture. Arguments are passed to Texture constructor 188 | // returns self (for chaining) 189 | template 190 | Mesh& add_texture(Args&&... args) { 191 | textures[Type].emplace_back(std::forward(args)...); 192 | return *this; 193 | } 194 | 195 | // Set specular shininess parameter 196 | Mesh& set_shininess(float val); 197 | 198 | // * Transformations 199 | // Apply translation 200 | Mesh& translate(const Eigen::Ref& vec); 201 | // Set translation 202 | Mesh& set_translation(const Eigen::Ref& vec); 203 | 204 | // Apply rotation (3x3 rotation matrix) 205 | Mesh& rotate(const Eigen::Ref& mat); 206 | 207 | // Apply scaling 208 | Mesh& scale(const Eigen::Ref& vec); 209 | // Apply uniform scaling 210 | Mesh& scale(float val); 211 | 212 | // Apply transform (4x4 affine transform matrix) 213 | Mesh& apply_transform(const Eigen::Ref& mat); 214 | // Set transform (4x4 affine transform matrix) 215 | Mesh& set_transform(const Eigen::Ref& mat); 216 | 217 | // Init or update VAO/VBO/EBO buffers from current vertex and triangle data 218 | // Must called before first draw for each GLFW context to ensure 219 | // textures are reconstructed. 220 | void update(bool force_init = false); 221 | 222 | // ADVANCED: Free buffers. Used automatically in destructor. 223 | void free_bufs(); 224 | 225 | // * Accessors 226 | // Position data of vertices (#verts, 3). 227 | inline Eigen::Ref verts_pos() { return data.leftCols<3>(); } 228 | 229 | // The optional RGB data of each vertex (#verts, 3). 230 | inline Eigen::Ref verts_rgb() { return data.middleCols<3>(3); } 231 | 232 | // ADVANCED: Normal vectors data (#verts, 3). 233 | // If called, this disables automatic normal computation. 234 | inline Eigen::Ref verts_norm() { 235 | _auto_normals = false; 236 | return data.rightCols<3>(); 237 | } 238 | 239 | // Enable/disable object 240 | Mesh& enable(bool val = true); 241 | 242 | // * VERY basic OBJ I/O 243 | // - Only supports v and f types 244 | // - Each f row must consist of three integers (triangle indices) 245 | // - Each v row may consist of 3 OR 6 floats (position, or position+rgb 246 | // color) 247 | void save_basic_obj(const std::string& path) const; 248 | void load_basic_obj(const std::string& path); 249 | 250 | // * Example meshes 251 | // Triangle 252 | static Mesh Triangle(const Eigen::Ref& a, 253 | const Eigen::Ref& b, 254 | const Eigen::Ref& c); 255 | // Square centered at 0,0,0 with normal in z direction and side length 1 256 | static Mesh Square(); 257 | 258 | // Cube centered at 0,0,0 with side length 1 259 | static Mesh Cube(); 260 | 261 | // UV sphere centered at 0,0,0 with radius 1 262 | static Mesh Sphere(int rings = 30, int sectors = 30); 263 | 264 | // Shape (num_verts, 9) 265 | // 3 x vertex position 266 | // 3 x vertex color data (either uv coords, or rgb color, depending on 267 | // shading_type) 3 x normal 268 | // TODO: make some of the columns optional/only transfer partly 269 | // to save data transfer time and memory 270 | PointsRGBNormal data; 271 | 272 | // Shape (num_triangles, 3) 273 | // Triangle indices, empty if num_triangles = -1 (not using EBO) 274 | Triangles faces; 275 | 276 | // Whether this mesh is enabled; if false, does not draw anything 277 | bool enabled = true; 278 | 279 | // Textures 280 | std::array, Texture::__TYPE_COUNT> textures; 281 | 282 | // Shininess 283 | float shininess = 10.f; 284 | 285 | // Model local transfom 286 | Matrix4f transform; 287 | 288 | // Shading type : interpretation of middle 3 columns in verts 289 | // the right shader will be chosen by viewer (mesh does not have control) 290 | ShadingType shading_type = ShadingType::vertex; 291 | 292 | private: 293 | // Generate a white 1x1 texture to blank_tex_id 294 | // used to fill maps if no texture provided 295 | void gen_blank_texture(); 296 | 297 | // Vertex Array Object index 298 | Index VAO = -1; 299 | 300 | Index VBO = -1, EBO = -1; 301 | Index blank_tex_id = -1; 302 | 303 | // Texture data 304 | Points2D _tex_coords; 305 | Triangles _tex_faces; 306 | 307 | // Data, arranged in texture coord indexing 308 | PointsRGBNormal _data_tex; 309 | // Map from texture coord index -> vertex index 310 | Eigen::Matrix _tex_to_vert; 311 | 312 | // Whether to use automatic normal estimation (get normals automatically on 313 | // update) 314 | bool _auto_normals = true; 315 | }; 316 | 317 | // Represents a 3D point cloud with vertices (including uv, normals) 318 | // where each vertex has a color. 319 | // Also supports drawing the points as a polyline (call draw_lines()). 320 | class PointCloud { 321 | public: 322 | explicit PointCloud(size_t num_verts = 0); 323 | // Set vertices with positions pos with colors rgb 324 | explicit PointCloud(const Eigen::Ref& pos, 325 | const Eigen::Ref& rgb); 326 | // Set all points to same color 327 | // (can't put Eigen::Vector3f due 'ambiguity' with above) 328 | explicit PointCloud(const Eigen::Ref& pos, float r = 1.f, 329 | float g = 1.f, float b = 1.f); 330 | 331 | ~PointCloud(); 332 | 333 | // Resize point cloud (destroys current data) 334 | void resize(size_t num_verts); 335 | 336 | // Draw mesh to shader wrt camera 337 | void draw(Index shader_id, const Camera& camera); 338 | 339 | // Position part of verts 340 | inline Eigen::Ref verts_pos() { return data.leftCols<3>(); } 341 | // RGB part of verts 342 | inline Eigen::Ref verts_rgb() { return data.rightCols<3>(); } 343 | 344 | // Enable/disable object 345 | PointCloud& enable(bool val = true); 346 | // Set the point size for drawing 347 | inline PointCloud& set_point_size(float val) { 348 | point_size = val; 349 | return *this; 350 | } 351 | // Draw polylines between consecutive points 352 | inline PointCloud& draw_lines() { 353 | lines = true; 354 | return *this; 355 | } 356 | 357 | // Apply translation 358 | PointCloud& translate(const Eigen::Ref& vec); 359 | // Set translation 360 | PointCloud& set_translation(const Eigen::Ref& vec); 361 | 362 | // Apply rotation (3x3 rotation matrix) 363 | PointCloud& rotate(const Eigen::Ref& mat); 364 | // Apply scaling 365 | PointCloud& scale(const Eigen::Ref& vec); 366 | // Apply uniform scaling in each dimension 367 | PointCloud& scale(float val); 368 | 369 | // Apply transform (4x4 affine transform matrix) 370 | PointCloud& apply_transform(const Eigen::Ref& mat); 371 | // Set transform (4x4 affine transform matrix) 372 | PointCloud& set_transform(const Eigen::Ref& mat); 373 | 374 | // Init or update VAO/VBO buffers from current vertex data 375 | // Must called before first draw for each GLFW context to ensure 376 | // textures are reconstructed. 377 | // force_init: INTERNAL, whether to force recreating buffers, DO NOT use 378 | // this 379 | void update(bool force_init = false); 380 | 381 | // ADVANCED: Free buffers. Used automatically in destructor. 382 | void free_bufs(); 383 | 384 | // * Example point clouds/lines 385 | static PointCloud Line( 386 | const Eigen::Ref& a, 387 | const Eigen::Ref& b, 388 | const Eigen::Ref& color = Vector3f(1.f, 1.f, 1.f)); 389 | 390 | // Data store 391 | PointsRGB data; 392 | 393 | // Whether this point cloud is enabled; if false, does not draw anything 394 | bool enabled = true; 395 | 396 | // If true, draws polylines between vertices 397 | // If false (default), draws points only 398 | bool lines = false; 399 | 400 | // Point size (if lines = false) 401 | float point_size = 1.f; 402 | 403 | // Model local transfom 404 | Matrix4f transform; 405 | 406 | private: 407 | // Buffer indices 408 | Index VAO = -1, VBO = -1; 409 | }; 410 | 411 | // MeshView OpenGL 3D viewer 412 | class Viewer { 413 | public: 414 | Viewer(); 415 | ~Viewer(); 416 | 417 | // Show window and start render loop; blocks execution 418 | // press q/ESC to close window and exit loop 419 | void show(); 420 | 421 | // Add mesh (to Viewer::meshes), arguments are forwarded to Mesh constructor 422 | template 423 | Mesh& add_mesh(Args&&... args) { 424 | meshes.push_back(std::make_unique(std::forward(args)...)); 425 | if (_looping) meshes.back()->update(); 426 | return *meshes.back(); 427 | } 428 | // Add point_cloud (to Viewer::point_clouds) 429 | // arguments are forwarded to PointCloud constructor 430 | template 431 | PointCloud& add_point_cloud(Args&&... args) { 432 | point_clouds.push_back( 433 | std::make_unique(std::forward(args)...)); 434 | if (_looping) point_clouds.back()->update(); 435 | return *point_clouds.back(); 436 | } 437 | // Add a cube centered at cen with given side length. 438 | // Mesh will have identity transform (points are moved physically in the 439 | // mesh) 440 | Mesh& add_cube(const Eigen::Ref& cen = Vector3f(0.f, 0.f, 441 | 0.f), 442 | float side_len = 1.0f, 443 | const Eigen::Ref& color = Vector3f(1.f, 0.5f, 444 | 0.f)); 445 | 446 | // Add a square centered at cen with given side length, normal to the 447 | // +z-axis. Mesh will have identity transform (points are moved physically 448 | // in the mesh) 449 | Mesh& add_square( 450 | const Eigen::Ref& cen = Vector3f(0.f, 0.f, 0.f), 451 | float side_len = 1.0f, 452 | const Eigen::Ref& color = Vector3f(1.f, 0.5f, 0.f)); 453 | 454 | // Add a UV sphere centered at cen with given radius. 455 | // Mesh will have identity transform (points are moved physically in the 456 | // mesh) 457 | Mesh& add_sphere( 458 | const Eigen::Ref& cen = Vector3f(0.f, 0.f, 0.f), 459 | float radius = 1.0f, 460 | const Eigen::Ref& color = Vector3f(1.f, 0.5f, 0.f), 461 | int rings = 30, int sectors = 30); 462 | 463 | // Add a line (PointCloud with line rendering). 464 | // Point cloud will have identity transform (not translated or scaled) 465 | PointCloud& add_line( 466 | const Eigen::Ref& a, 467 | const Eigen::Ref& b, 468 | const Eigen::Ref& color = Vector3f(1.f, 1.f, 1.f)); 469 | 470 | // * The meshes 471 | std::vector> meshes; 472 | // * The point clouds 473 | std::vector> point_clouds; 474 | 475 | // * Lighting 476 | // Ambient light color, default 0.2 0.2 0.2 477 | Vector3f light_color_ambient; 478 | 479 | // Point light position (in VIEW space, so that light follows camera) 480 | Vector3f light_pos; 481 | // Light color diffuse/specular, default white 482 | Vector3f light_color_diffuse; 483 | Vector3f light_color_specular; 484 | 485 | // * Camera 486 | Camera camera; 487 | 488 | // * Render params 489 | // Axes? (a) 490 | bool draw_axes = true; 491 | // Wireframe mode? (w) 492 | bool wireframe = false; 493 | // Backface culling? (c) 494 | bool cull_face = true; 495 | // Whether to wait for event on loop 496 | // true: loops on user input (glfwWaitEvents), saves power and computation 497 | // false: loops continuously (glfwPollEvents), useful for e.g. animation 498 | bool loop_wait_events = true; 499 | 500 | // * Aesthetics 501 | // Window title, updated on show() calls only (i.e. please set before 502 | // show()) 503 | std::string title = "meshview"; 504 | 505 | // Background color 506 | Vector3f background; 507 | 508 | // * Event callbacks 509 | // Called after GL cnotext init 510 | std::function on_open; 511 | // Called when window is about to close 512 | std::function on_close; 513 | // Called per iter of render loop, before on_gui 514 | // return true if mesh/point cloud/camera data has been updated, false 515 | // otherwise 516 | std::function on_loop; 517 | #ifdef MESHVIEW_IMGUI 518 | // Called per iter of render loop, after on_loop 519 | // only avaiable if MESHVIEW_IMGUI defined. 520 | // Within the function, Dear ImGui is already set up, 521 | // ie. ready to use ImGui::Begin etc 522 | std::function on_gui; 523 | #endif 524 | // Called on key up/down/repeat: args (key, action, mods), return false to 525 | // prevent default see https://www.glfw.org/docs/3.3/group__mods.html on 526 | // mods 527 | std::function on_key; 528 | // Called on mouse up/down/repeat: args (button, action, mods), return false 529 | // to prevent default see https://www.glfw.org/docs/3.3/group__mods.html on 530 | // mods 531 | std::function on_mouse_button; 532 | // Called on mouse move: args(x, y) return false to prevent default 533 | std::function on_mouse_move; 534 | // Called on mouse scroll: args(xoffset, yoffset) return false to prevent 535 | // default 536 | std::function on_scroll; 537 | 538 | // * Dynamic data (advanced, for use in callbacks) 539 | // Window width/height, as set in system 540 | // (don't modify after show() and before window close) 541 | int _width = 1000, _height = 600; 542 | 543 | // Mouse position, if available (don't modify) 544 | double _mouse_x, _mouse_y; 545 | 546 | // Mouse button and modifier keys; mouse_button is -1 if nothing down (don't 547 | // modify) 548 | int _mouse_button = -1, _mouse_mods; 549 | 550 | // Window pos/size prior to full screen 551 | int _fullscreen_backup[4]; 552 | 553 | // Is window in fullscreen? (do not modify) 554 | bool _fullscreen; 555 | 556 | // ADNANCED: Pointer to GLFW window object 557 | void* _window = nullptr; 558 | 559 | private: 560 | // True only during the render loop (show()) 561 | bool _looping = false; 562 | }; 563 | 564 | } // namespace meshview 565 | 566 | #endif // ifndef MESHVIEW_B1FE2D07_A12E_4C8B_A673_D9AC48841D24 567 | -------------------------------------------------------------------------------- /include/meshview/meshview_imgui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MESHVIEW_IMGUI_FA8950B7_4B5D_4B58_A95A_46172CD48B21 3 | #define MESHVIEW_IMGUI_FA8950B7_4B5D_4B58_A95A_46172CD48B21 4 | 5 | #include "meshview/common.hpp" 6 | 7 | #ifdef MESHVIEW_IMGUI 8 | #define IMGUI_IMPL_OPENGL_LOADER_GLEW 9 | #include "meshview/imgui/imgui_impl_opengl3.h" 10 | #include "meshview/imgui/imgui_impl_glfw.h" 11 | #include "meshview/imgui/imgui_stdlib.h" 12 | #endif 13 | 14 | #endif // ifndef MESHVIEW_IMGUI_FA8950B7_4B5D_4B58_A95A_46172CD48B21 15 | -------------------------------------------------------------------------------- /include/meshview/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MESHVIEW_UTIL_67A492E2_6CCA_4FA8_9763_90A5DA4F6837 3 | #define MESHVIEW_UTIL_67A492E2_6CCA_4FA8_9763_90A5DA4F6837 4 | 5 | #include 6 | #include "meshview/common.hpp" 7 | 8 | namespace meshview { 9 | namespace util { 10 | 11 | // Perspective projection matrix 12 | // xscale/yscale: scaling on x/y axes 13 | // z_near/z_far: clip distances 14 | Matrix4f persp(float xscale, float yscale, float z_near, float z_far); 15 | 16 | // Returns 'look at' matrix 17 | // IMPORTANT: fw is normalized forward direction, 18 | // NOT the point to look at! 19 | // pos: camera position 20 | // fw: forward direction (must be normalized) 21 | // up: up direction for camera (must be normalized) 22 | Matrix4f look_at(const Eigen::Ref& pos, 23 | const Eigen::Ref& fw, 24 | const Eigen::Ref& up); 25 | 26 | 27 | // Estimate normals given points in pointcloud with given *triangles* 28 | // outputs normals into out. If faces is empty, same as 'no element buffer' version. 29 | // NOTE: out must already be of same size as verts 30 | void estimate_normals(const Eigen::Ref& verts, 31 | const Eigen::Ref& faces, 32 | Eigen::Ref out); 33 | 34 | // Estimate normals given points in pointcloud with no element buffer 35 | // (point 0,1,2 are 1st triangle, 3,4,5 2nd, etc..) 36 | // outputs normals into out. 37 | // NOTE: out must already be of same size as verts 38 | void estimate_normals(const Eigen::Ref& verts, 39 | Eigen::Ref out); 40 | 41 | 42 | // From list of triangles and uv triangles (same # rows), 43 | // construct a map from uv vertex indices to vertex indices 44 | Eigen::Matrix make_uv_to_vert_map( 45 | size_t num_uv_verts, 46 | const Eigen::Ref& tri_faces, 47 | const Eigen::Ref& uv_tri_faces); 48 | 49 | } // namespace util 50 | } // namespace meshview 51 | 52 | #endif // ifndef MESHVIEW_UTIL_67A492E2_6CCA_4FA8_9763_90A5DA4F6837 53 | -------------------------------------------------------------------------------- /pybind.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | using namespace meshview; 13 | 14 | namespace py = pybind11; 15 | 16 | namespace { 17 | std::map bools; 18 | std::map floats; 19 | std::map ints; 20 | std::map strs; 21 | } // namespace 22 | 23 | PYBIND11_MODULE(meshview, m) { 24 | m.doc() = R"pbdoc(meshview: mesh and point cloud viewer)pbdoc"; 25 | 26 | auto input = m.def_submodule("input"); 27 | py::enum_(input, "Action") 28 | .value("press", input::Action::press) 29 | .value("release", input::Action::release) 30 | .value("repat", input::Action::repeat); 31 | 32 | py::enum_(m, "ShadingType") 33 | .value("texture", Mesh::ShadingType::texture) 34 | .value("vertex", Mesh::ShadingType::vertex); 35 | 36 | py::class_(m, "Mesh") 37 | .def("update", &Mesh::update, py::arg("force_init") = false) 38 | .def("save_basic_obj", &Mesh::save_basic_obj) 39 | .def("load_basic_obj", &Mesh::load_basic_obj) 40 | .def("resize", &Mesh::resize, py::arg("num_verts"), 41 | py::arg("num_triangles") = 0) 42 | .def_property_readonly("n_verts", 43 | [](Mesh& self) { return self.data.rows(); }) 44 | .def_property_readonly("n_faces", 45 | [](Mesh& self) { return self.faces.rows(); }) 46 | .def("set_tex_coords", &Mesh::set_tex_coords, py::arg("coords"), 47 | py::arg("tri_faces"), py::return_value_policy::reference_internal) 48 | .def("unset_tex_coords", &Mesh::unset_tex_coords, 49 | py::return_value_policy::reference_internal) 50 | .def( 51 | "add_texture", 52 | [](Mesh& self, const std::string& path, bool flip_y) { 53 | self.add_texture(path, flip_y); 54 | }, 55 | py::arg("image_path"), py::arg("flip_y") = true) 56 | .def( 57 | "add_texture", 58 | [](Mesh& self, float r, float g, float b) { 59 | self.add_texture(r, g, b); 60 | }, 61 | py::arg("r"), py::arg("g"), py::arg("b")) 62 | .def( 63 | "add_texture", 64 | [](Mesh& self, const Eigen::Ref& image, 65 | int n_channels) { self.add_texture(image, n_channels); }, 66 | py::arg("image"), py::arg("n_channels") = 1) 67 | .def( 68 | "add_texture_specular", 69 | [](Mesh& self, const std::string& path, bool flip_y) { 70 | self.add_texture(path, flip_y); 71 | }, 72 | py::arg("image_path"), py::arg("flip_y") = true) 73 | .def( 74 | "add_texture_specular", 75 | [](Mesh& self, float r, float g, float b) { 76 | self.add_texture(r, g, b); 77 | }, 78 | py::arg("r"), py::arg("g"), py::arg("b")) 79 | .def( 80 | "add_texture_specular", 81 | [](Mesh& self, const Eigen::Ref& image, 82 | int n_channels) { 83 | self.add_texture(image, n_channels); 84 | }, 85 | py::arg("image"), py::arg("n_channels") = 1) 86 | .def("clear_textures", 87 | [](Mesh& self) { self.textures[Texture::TYPE_DIFFUSE].clear(); }) 88 | .def("clear_textures_specular", 89 | [](Mesh& self) { self.textures[Texture::TYPE_SPECULAR].clear(); }) 90 | .def("translate", &Mesh::translate, 91 | py::return_value_policy::reference_internal) 92 | .def("set_translation", &Mesh::set_translation, 93 | py::return_value_policy::reference_internal) 94 | .def( 95 | "scale", [](Mesh& self, float scale) { return self.scale(scale); }, 96 | py::return_value_policy::reference_internal) 97 | .def( 98 | "scale", 99 | [](Mesh& self, Eigen::Ref scale) { 100 | return self.scale(scale); 101 | }, 102 | py::return_value_policy::reference_internal) 103 | .def("rotate", &Mesh::rotate, 104 | py::return_value_policy::reference_internal) 105 | .def("apply_transform", &Mesh::apply_transform, 106 | py::return_value_policy::reference_internal) 107 | .def("set_transform", &Mesh::set_transform) 108 | .def("set_shininess", &Mesh::set_shininess, 109 | py::return_value_policy::reference_internal) 110 | .def_readwrite("data", &Mesh::data) 111 | .def_property( 112 | "verts_pos", 113 | [](Mesh& self) -> Eigen::Ref { return self.verts_pos(); }, 114 | [](Mesh& self, Eigen::Ref val) { 115 | self.verts_pos() = val; 116 | }) 117 | .def_property( 118 | "verts_rgb", 119 | [](Mesh& self) -> Eigen::Ref { return self.verts_rgb(); }, 120 | [](Mesh& self, Eigen::Ref val) { 121 | self.verts_rgb() = val; 122 | }) 123 | .def_property( 124 | "verts_norm", 125 | [](Mesh& self) -> Eigen::Ref { return self.verts_norm(); }, 126 | [](Mesh& self, Eigen::Ref val) { 127 | self.verts_norm() = val; 128 | }) 129 | .def_readwrite("faces", &Mesh::faces) 130 | .def_readwrite("enabled", &Mesh::enabled) 131 | .def_readwrite("shininess", &Mesh::shininess) 132 | .def_readwrite("shading_type", &Mesh::shading_type) 133 | .def_readwrite("transform", &Mesh::transform); 134 | 135 | py::class_(m, "PointCloud") 136 | .def("update", &PointCloud::update, py::arg("force_init") = false) 137 | .def("resize", &PointCloud::resize, py::arg("num_verts")) 138 | .def_property_readonly( 139 | "n_verts", [](PointCloud& self) { return self.data.rows(); }) 140 | .def("translate", &PointCloud::translate, 141 | py::return_value_policy::reference_internal) 142 | .def("set_translation", &PointCloud::set_translation, 143 | py::return_value_policy::reference_internal) 144 | .def( 145 | "scale", 146 | [](PointCloud& self, float scale) { return self.scale(scale); }, 147 | py::return_value_policy::reference_internal) 148 | .def( 149 | "scale", 150 | [](PointCloud& self, Eigen::Ref scale) { 151 | return self.scale(scale); 152 | }, 153 | py::return_value_policy::reference_internal) 154 | .def("rotate", &PointCloud::rotate, 155 | py::return_value_policy::reference_internal) 156 | .def("apply_transform", &PointCloud::apply_transform, 157 | py::return_value_policy::reference_internal) 158 | .def("set_transform", &PointCloud::set_transform, 159 | py::return_value_policy::reference_internal) 160 | .def("set_point_size", &PointCloud::set_point_size, 161 | py::return_value_policy::reference_internal) 162 | .def("draw_lines", &PointCloud::draw_lines, 163 | py::return_value_policy::reference_internal) 164 | .def_readwrite("data", &PointCloud::data) 165 | .def_property( 166 | "verts_pos", 167 | [](PointCloud& self) -> Eigen::Ref { 168 | return self.verts_pos(); 169 | }, 170 | [](PointCloud& self, Eigen::Ref val) { 171 | self.verts_pos() = val; 172 | }) 173 | .def_property( 174 | "verts_rgb", 175 | [](PointCloud& self) -> Eigen::Ref { 176 | return self.verts_rgb(); 177 | }, 178 | [](PointCloud& self, Eigen::Ref val) { 179 | self.verts_rgb() = val; 180 | }) 181 | .def_readwrite("enabled", &PointCloud::enabled) 182 | .def_readwrite("transform", &PointCloud::transform) 183 | .def_readwrite("point_size", &PointCloud::point_size) 184 | .def_readwrite("lines", &PointCloud::lines, 185 | "If true, draws polylines instead of points"); 186 | 187 | py::class_(m, "Camera") 188 | .def("update_view", &Camera::update_view) 189 | .def("update_proj", &Camera::update_proj) 190 | .def("reset_view", &Camera::reset_view) 191 | .def("reset_proj", &Camera::reset_proj) 192 | 193 | // * View parameters 194 | // Center of rotation 195 | .def_readwrite("center_of_rot", &Camera::center_of_rot) 196 | // Distance to cor 197 | .def_readwrite("dist_to_center", &Camera::dist_to_center) 198 | // Euler angles 199 | .def_readwrite("yaw", &Camera::yaw) 200 | .def_readwrite("pitch", &Camera::pitch) 201 | .def_readwrite("roll", &Camera::roll) 202 | 203 | // * Projection parameters 204 | // Orthographic? 205 | .def_readwrite("ortho", &Camera::ortho) 206 | // Field of view, aspect ratio 207 | .def_readwrite("fovy", &Camera::fovy) 208 | .def_readwrite("aspect", &Camera::aspect) 209 | // Clip distances 210 | .def_readwrite("z_close", &Camera::z_close) 211 | .def_readwrite("z_far", &Camera::z_far) 212 | 213 | // Computed matrices 214 | .def_readonly("view", &Camera::view) 215 | .def_readonly("proj", &Camera::proj) 216 | 217 | // Computed vectors 218 | .def_readonly("front", &Camera::front) 219 | .def_readonly("up", &Camera::up) 220 | .def_readonly("world_up", &Camera::world_up); 221 | 222 | py::class_(m, "Viewer") 223 | .def(py::init<>()) 224 | .def( 225 | "add_mesh", 226 | [](Viewer& self, const std::string& path) -> Mesh& { 227 | return self.add_mesh(path); 228 | }, 229 | py::arg("path"), py::return_value_policy::reference_internal) 230 | .def( 231 | "add_mesh", 232 | [](Viewer& self, int num_verts, int num_faces) -> Mesh& { 233 | return self.add_mesh(num_verts, num_faces); 234 | }, 235 | py::arg("num_verts"), py::arg("num_faces") = 0, 236 | py::return_value_policy::reference_internal) 237 | .def( 238 | "add_mesh", 239 | [](Viewer& self, const Eigen::Ref& verts, 240 | const Eigen::Ref& tri_faces, 241 | const Eigen::Ref& rgb, 242 | const Eigen::Ref& normals) -> Mesh& { 243 | return self.add_mesh(verts, tri_faces, rgb, normals); 244 | }, 245 | py::arg("verts"), py::arg("tri_faces"), py::arg("rgb"), 246 | py::arg("normals") = Points(), 247 | py::return_value_policy::reference_internal) 248 | .def( 249 | "add_mesh", 250 | [](Viewer& self, const Eigen::Ref& verts, 251 | const Eigen::Ref& tri_faces, float r, float g, 252 | float b, const Eigen::Ref& normals) -> Mesh& { 253 | return self.add_mesh(verts, tri_faces, r, g, b, normals); 254 | }, 255 | py::arg("verts"), py::arg("tri_faces") = Triangles(), 256 | py::arg("r") = 1.f, py::arg("g") = 1.f, py::arg("b") = 1.f, 257 | py::arg("normals") = Points(), 258 | py::return_value_policy::reference_internal) 259 | .def( 260 | "add_point_cloud", 261 | [](Viewer& self, int num_verts) -> PointCloud& { 262 | return self.add_point_cloud(num_verts); 263 | }, 264 | py::arg("num_verts"), py::return_value_policy::reference_internal) 265 | .def( 266 | "add_point_cloud", 267 | [](Viewer& self, const Eigen::Ref& verts, 268 | const Eigen::Ref& rgb) -> PointCloud& { 269 | return self.add_point_cloud(verts, rgb); 270 | }, 271 | py::arg("verts"), py::arg("rgb"), 272 | py::return_value_policy::reference_internal) 273 | .def( 274 | "add_point_cloud", 275 | [](Viewer& self, const Eigen::Ref& verts, float r, 276 | float g, float b) -> PointCloud& { 277 | return self.add_point_cloud(verts, r, g, b); 278 | }, 279 | py::arg("verts"), py::arg("r") = 1.f, py::arg("g") = 1.f, 280 | py::arg("b") = 1.f, py::return_value_policy::reference_internal) 281 | .def("add_line", &Viewer::add_line, py::arg("a"), py::arg("b"), 282 | py::arg("color") = Eigen::Vector3f(1.f, 1.f, 1.f), 283 | py::return_value_policy::reference_internal) 284 | .def("add_cube", &Viewer::add_cube, 285 | py::arg("cen") = Eigen::Vector3f(0.f, 0.f, 0.f), 286 | py::arg("side_len") = 1.0f, 287 | py::arg("color") = Eigen::Vector3f(1.f, 0.5f, 0.f), 288 | py::return_value_policy::reference_internal) 289 | .def("add_square", &Viewer::add_square, 290 | py::arg("cen") = Eigen::Vector3f(0.f, 0.f, 0.f), 291 | py::arg("side_len") = 1.0f, 292 | py::arg("color") = Eigen::Vector3f(1.f, 0.5f, 0.f), 293 | py::return_value_policy::reference_internal) 294 | .def("add_sphere", &Viewer::add_sphere, 295 | py::arg("cen") = Eigen::Vector3f(0.f, 0.f, 0.f), 296 | py::arg("radius") = 1.0f, 297 | py::arg("color") = Eigen::Vector3f(1.f, 0.5f, 0.f), 298 | py::arg("rings") = 30, py::arg("sections") = 30, 299 | py::return_value_policy::reference_internal) 300 | .def_readwrite("camera", &Viewer::camera) 301 | .def_readwrite("title", &Viewer::title) 302 | .def_readwrite("cull_face", &Viewer::cull_face) 303 | .def_readwrite("wireframe", &Viewer::wireframe) 304 | .def_readwrite("draw_axes", &Viewer::draw_axes) 305 | .def_readwrite("background", &Viewer::background) 306 | .def_readwrite("light_pos", &Viewer::light_pos) 307 | .def_readwrite("light_color_ambient", &Viewer::light_color_ambient) 308 | .def_readwrite("light_color_diffuse", &Viewer::light_color_diffuse) 309 | .def_readwrite("light_color_specular", &Viewer::light_color_specular) 310 | .def_readwrite("on_key", &Viewer::on_key) 311 | .def_readwrite("on_loop", &Viewer::on_loop) 312 | .def_readwrite("on_open", &Viewer::on_open) 313 | .def_readwrite("on_close", &Viewer::on_close) 314 | .def_readwrite("on_scroll", &Viewer::on_scroll) 315 | .def_readwrite("on_mouse_move", &Viewer::on_mouse_move) 316 | .def_readwrite("on_mouse_button", &Viewer::on_mouse_button) 317 | .def_property_readonly("n_meshes", 318 | [](Viewer& self) { return self.meshes.size(); }) 319 | .def_property_readonly( 320 | "n_point_clouds", 321 | [](Viewer& self) { return self.point_clouds.size(); }) 322 | .def( 323 | "get_mesh", 324 | [](Viewer& self, int idx) -> Mesh& { return *self.meshes[idx]; }, 325 | py::return_value_policy::reference_internal) 326 | .def( 327 | "get_point_cloud", 328 | [](Viewer& self, int idx) -> PointCloud& { 329 | return *self.point_clouds[idx]; 330 | }, 331 | py::return_value_policy::reference_internal) 332 | .def("remove_mesh", 333 | [](Viewer& self, int idx) { 334 | self.meshes.erase(self.meshes.begin() + idx); 335 | }) 336 | .def("remove_point_cloud", 337 | [](Viewer& self, int idx) { 338 | self.point_clouds.erase(self.point_clouds.begin() + idx); 339 | }) 340 | .def("clear_meshes", [](Viewer& self) { self.meshes.clear(); }) 341 | .def("clear_point_clouds", 342 | [](Viewer& self) { self.point_clouds.clear(); }) 343 | .def("show", &Viewer::show); 344 | } 345 | -------------------------------------------------------------------------------- /python_example.py: -------------------------------------------------------------------------------- 1 | import meshview 2 | 3 | v = meshview.Viewer() 4 | v.draw_axes = False 5 | 6 | sph = v.add_sphere([0, 0, 0], 0.5) 7 | sph.shading_type = meshview.ShadingType.vertex 8 | sph.verts_rgb[:] = [1, 0, 0] # red 9 | 10 | cube = v.add_cube([2, 0, 0], 0.25) 11 | 12 | v.show() 13 | -------------------------------------------------------------------------------- /readme-img/depth-cam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxyu/meshview/e8b8d8dbf185b1933bc54588ce8511d2be722ea7/readme-img/depth-cam.png -------------------------------------------------------------------------------- /readme-img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxyu/meshview/e8b8d8dbf185b1933bc54588ce8511d2be722ea7/readme-img/example.png -------------------------------------------------------------------------------- /readme-img/smpl-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxyu/meshview/e8b8d8dbf185b1933bc54588ce8511d2be722ea7/readme-img/smpl-visual.png -------------------------------------------------------------------------------- /sample-proj/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.3 ) 2 | project( meshview_example ) 3 | 4 | find_package( meshview REQUIRED ) 5 | add_executable( example ../example.cpp ) 6 | target_link_libraries( example meshview::meshview ) 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import platform 5 | import subprocess 6 | 7 | from setuptools import setup, Extension 8 | from setuptools.command.build_ext import build_ext 9 | from distutils.version import LooseVersion 10 | 11 | 12 | class CMakeExtension(Extension): 13 | def __init__(self, name, sourcedir=''): 14 | Extension.__init__(self, name, sources=[]) 15 | self.sourcedir = os.path.abspath(sourcedir) 16 | 17 | 18 | class CMakeBuild(build_ext): 19 | def run(self): 20 | try: 21 | out = subprocess.check_output(['cmake', '--version']) 22 | except OSError: 23 | raise RuntimeError( 24 | "CMake must be installed to build the following extensions: " + 25 | ", ".join(e.name for e in self.extensions)) 26 | 27 | if platform.system() == "Windows": 28 | cmake_version = LooseVersion( 29 | re.search(r'version\s*([\d.]+)', out.decode()).group(1)) 30 | if cmake_version < '3.1.0': 31 | raise RuntimeError("CMake >= 3.1.0 is required on Windows") 32 | 33 | for ext in self.extensions: 34 | self.build_extension(ext) 35 | 36 | def build_extension(self, ext): 37 | extdir = os.path.abspath( 38 | os.path.dirname(self.get_ext_fullpath(ext.name))) 39 | # required for auto-detection of auxiliary "native" libs 40 | if not extdir.endswith(os.path.sep): 41 | extdir += os.path.sep 42 | 43 | cmake_args = [ 44 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, 45 | '-DPYTHON_EXECUTABLE=' + sys.executable, 46 | '-DMESHVIEW_BUILD_PYTHON=ON', '-DMESHVIEW_BUILD_EXAMPLE=OFF', 47 | '-DMESHVIEW_BUILD_INSTALL=OFF' 48 | ] 49 | 50 | cfg = 'Debug' if self.debug else 'Release' 51 | build_args = ['--config', cfg] 52 | 53 | if platform.system() == "Windows": 54 | cmake_args += [ 55 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format( 56 | cfg.upper(), extdir) 57 | ] 58 | if sys.maxsize > 2**32: 59 | cmake_args += ['-A', 'x64'] 60 | build_args += ['--', '/m'] 61 | else: 62 | cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] 63 | import multiprocessing 64 | num_cpus = multiprocessing.cpu_count() 65 | build_args += ['--', '-j' + str(num_cpus)] 66 | 67 | env = os.environ.copy() 68 | env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format( 69 | env.get('CXXFLAGS', ''), self.distribution.get_version()) 70 | if not os.path.exists(self.build_temp): 71 | os.makedirs(self.build_temp) 72 | subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, 73 | cwd=self.build_temp, 74 | env=env) 75 | subprocess.check_call(['cmake', '--build', '.'] + build_args, 76 | cwd=self.build_temp) 77 | 78 | 79 | setup( 80 | name='meshview', 81 | version='0.0.1', 82 | author='Alex Yu', 83 | author_email='alexyu99126@gmail.com', 84 | description='meshview: Mesh and point cloud viewer', 85 | long_description='', 86 | ext_modules=[CMakeExtension('meshview')], 87 | cmdclass=dict(build_ext=CMakeBuild), 88 | zip_safe=False, 89 | ) 90 | -------------------------------------------------------------------------------- /src/camera.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/meshview.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "meshview/util.hpp" 8 | 9 | namespace meshview { 10 | 11 | Camera::Camera(const Vector3f& center_of_rot, const Vector3f& world_up, 12 | float dist_to_center, float yaw, float pitch, float roll, 13 | bool ortho, float fovy, float aspect, float z_close, float z_far) 14 | : center_of_rot(center_of_rot), 15 | world_up(world_up), 16 | dist_to_center(dist_to_center), 17 | yaw(yaw), 18 | pitch(pitch), 19 | roll(roll), 20 | ortho(ortho), 21 | fovy(fovy), 22 | aspect(aspect), 23 | z_close(z_close), 24 | z_far(z_far) { 25 | update_proj(); 26 | update_view(); 27 | } 28 | 29 | void Camera::rotate_with_mouse(float xoffset, float yoffset) { 30 | xoffset *= rotate_speed; 31 | yoffset *= rotate_speed; 32 | 33 | float cr = cos(roll), sr = sin(roll); 34 | yaw += xoffset * cr + yoffset * sr; 35 | pitch -= yoffset * cr + xoffset * sr; 36 | 37 | // Clamp pitch 38 | static const float PITCH_CLAMP = M_PI * 0.49999f; 39 | if (std::fabs(pitch) > PITCH_CLAMP) { 40 | pitch = PITCH_CLAMP * (pitch > 0.f ? 1.f : -1.f); 41 | yaw = yaw += M_PI; 42 | roll = roll += M_PI; 43 | } 44 | update_view(); 45 | } 46 | 47 | void Camera::roll_with_mouse(float xoffset, float yoffset) { 48 | xoffset *= rotate_speed; 49 | roll += xoffset; 50 | 51 | update_view(); 52 | } 53 | 54 | void Camera::pan_with_mouse(float xoffset, float yoffset) { 55 | xoffset *= pan_speed * dist_to_center; 56 | yoffset *= pan_speed * dist_to_center; 57 | 58 | center_of_rot += -xoffset * right + yoffset * up; 59 | update_view(); 60 | } 61 | 62 | void Camera::zoom_with_mouse(float amount) { 63 | if (amount < 0) 64 | dist_to_center *= scroll_factor; 65 | else 66 | dist_to_center *= 1.f / scroll_factor; 67 | update_view(); 68 | if (ortho) update_proj(); 69 | } 70 | 71 | void Camera::reset_view() { 72 | center_of_rot.setZero(); 73 | world_up = Vector3f(0.f, 1.f, 0.f); 74 | dist_to_center = 3.f; 75 | yaw = -M_PI / 2; 76 | pitch = roll = 0.0f; 77 | update_view(); 78 | } 79 | 80 | void Camera::reset_proj() { 81 | fovy = M_PI / 4.f; 82 | aspect = 5.f / 3.f; 83 | z_close = 0.01f; 84 | z_far = 1e3f; 85 | update_proj(); 86 | } 87 | 88 | void Camera::update_view() { 89 | front[0] = cos(yaw) * cos(pitch); 90 | front[1] = sin(pitch); 91 | front[2] = sin(yaw) * cos(pitch); 92 | pos = center_of_rot - front * dist_to_center; 93 | Eigen::AngleAxisf aa_roll(roll, front); 94 | right = front.cross(aa_roll * world_up).normalized(); 95 | up = right.cross(front); 96 | view = util::look_at(pos, front, up); 97 | } 98 | 99 | void Camera::update_proj() { 100 | if (ortho) { 101 | proj = Eigen::Matrix4f::Identity(); 102 | proj(0, 0) = 4.f / (aspect * dist_to_center); 103 | proj(1, 1) = 4.f / (dist_to_center); 104 | proj(2, 2) = -1.f / (z_far - z_close); 105 | proj(2, 3) = z_close / (z_far - z_close); 106 | } else { 107 | float tan_half_fovy = tan(fovy / 2.f); 108 | proj = util::persp(1.f / (tan_half_fovy * aspect), 1.f / tan_half_fovy, 109 | z_close, z_far); 110 | } 111 | } 112 | 113 | } // namespace meshview 114 | -------------------------------------------------------------------------------- /src/cmake/meshviewConfig.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(Threads) 4 | find_dependency(glfw3) 5 | find_dependency(Eigen3) 6 | set(OpenGL_GL_PREFERENCE GLVND) 7 | find_dependency(OpenGL) 8 | 9 | include("${CMAKE_CURRENT_LIST_DIR}/meshviewTargets.cmake") 10 | -------------------------------------------------------------------------------- /src/mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/meshview.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "meshview/util.hpp" 11 | #include "meshview/internal/shader.hpp" 12 | #include "meshview/internal/assert.hpp" 13 | 14 | namespace meshview { 15 | namespace { 16 | 17 | void shader_set_transform_matrices(const internal::Shader& shader, 18 | const Camera& camera, 19 | const Matrix4f& transform) { 20 | shader.set_mat4("M", transform); 21 | shader.set_mat4("MVP", camera.proj * camera.view * transform); 22 | 23 | auto normal_matrix = transform.topLeftCorner<3, 3>().inverse().transpose(); 24 | shader.set_mat3("NormalMatrix", normal_matrix); 25 | } 26 | 27 | } // namespace 28 | 29 | // *** Mesh *** 30 | Mesh::Mesh(size_t num_verts, size_t num_triangles) : VAO((Index)-1) { 31 | resize(num_verts, num_triangles); 32 | } 33 | 34 | Mesh::Mesh(const std::string& path) { load_basic_obj(path); } 35 | 36 | Mesh::Mesh(const Eigen::Ref& pos, 37 | const Eigen::Ref& tri_faces, 38 | const Eigen::Ref& rgb, 39 | const Eigen::Ref& normals) 40 | : Mesh(pos.rows(), tri_faces.rows()) { 41 | if (tri_faces.rows()) faces.noalias() = tri_faces; 42 | verts_pos().noalias() = pos; 43 | if (rgb.rows()) { 44 | _MESHVIEW_ASSERT_EQ(rgb.rows(), pos.rows()); 45 | verts_rgb().noalias() = rgb; 46 | } 47 | _auto_normals = !normals.rows(); 48 | if (!_auto_normals) { 49 | _MESHVIEW_ASSERT_EQ(normals.rows(), pos.rows()); 50 | verts_norm().noalias() = normals; 51 | } 52 | } 53 | 54 | Mesh::Mesh(const Eigen::Ref& pos, 55 | const Eigen::Ref& tri_faces, float r, float g, 56 | float b, const Eigen::Ref& normals) 57 | : Mesh(pos.rows(), tri_faces.rows()) { 58 | _MESHVIEW_ASSERT_LT(0, pos.rows()); 59 | if (tri_faces.rows()) faces.noalias() = tri_faces; 60 | verts_pos().noalias() = pos; 61 | verts_rgb().rowwise() = Eigen::RowVector3f(r, g, b); 62 | _auto_normals = !normals.rows(); 63 | if (!_auto_normals) { 64 | _MESHVIEW_ASSERT_EQ(normals.rows(), pos.rows()); 65 | verts_norm().noalias() = normals; 66 | } 67 | } 68 | 69 | Mesh::~Mesh() { free_bufs(); } 70 | 71 | void Mesh::resize(size_t num_verts, size_t num_triangles) { 72 | data.resize(num_verts, data.ColsAtCompileTime); 73 | if (num_triangles == 0) { 74 | _MESHVIEW_ASSERT_EQ(num_verts % 3, 0); 75 | faces.resize(num_verts / 3, 3); 76 | for (Index i = 0; i < (Index)num_verts; ++i) { 77 | faces.data()[i] = i; 78 | } 79 | } else { 80 | faces.resize(num_triangles, faces.ColsAtCompileTime); 81 | } 82 | transform.setIdentity(); 83 | } 84 | 85 | void Mesh::draw(Index shader_id, const Camera& camera) { 86 | if (!enabled || data.rows() == 0) return; 87 | if (!~VAO) { 88 | std::cerr << "ERROR: Please call meshview::Mesh::update() before " 89 | "Mesh::draw()\n"; 90 | return; 91 | } 92 | internal::Shader shader(shader_id); 93 | 94 | if (shading_type == ShadingType::texture) { 95 | // Bind appropriate textures 96 | for (int ttype = 0; ttype < Texture::__TYPE_COUNT; ++ttype) { 97 | const char* ttype_name = Texture::type_to_name(ttype); 98 | if (textures[ttype].empty()) { 99 | // No texture, create default (grey) 100 | gen_blank_texture(); 101 | shader.set_int("material." + std::string(ttype_name), 0); 102 | glActiveTexture(GL_TEXTURE0); 103 | glBindTexture(GL_TEXTURE_2D, blank_tex_id); 104 | } 105 | } 106 | Index tex_id = 1; 107 | for (int ttype = 0; ttype < Texture::__TYPE_COUNT; ++ttype) { 108 | const char* ttype_name = Texture::type_to_name(ttype); 109 | auto& tex_vec = textures[ttype]; 110 | Index cnt = 0; 111 | for (size_t i = tex_vec.size() - 1; ~i; --i, ++tex_id) { 112 | glActiveTexture( 113 | GL_TEXTURE0 + 114 | tex_id); // Active proper texture unit before binding 115 | // Now set the sampler to the correct texture unit 116 | shader.set_int("material." + std::string(ttype_name) + 117 | (cnt ? std::to_string(cnt) : ""), 118 | tex_id); 119 | ++cnt; 120 | // And finally bind the texture 121 | glBindTexture(GL_TEXTURE_2D, tex_vec[i].id); 122 | } 123 | } 124 | } 125 | shader.set_float("material.shininess", shininess); 126 | 127 | // Set space transform matrices 128 | shader_set_transform_matrices(shader, camera, transform); 129 | 130 | // Draw mesh 131 | glBindVertexArray(VAO); 132 | glDrawElements(GL_TRIANGLES, (GLsizei)faces.size(), GL_UNSIGNED_INT, 0); 133 | glBindVertexArray(0); 134 | 135 | // Always good practice to set everything back to defaults once configured. 136 | glActiveTexture(GL_TEXTURE0); 137 | } 138 | 139 | Mesh& Mesh::set_tex_coords(const Eigen::Ref& coords, 140 | const Eigen::Ref& tri_faces) { 141 | // Each tex coord can be matched to at most one vertex, 142 | // so the number of vertices <= tex coords 143 | _MESHVIEW_ASSERT_LE(data.rows(), coords.rows()); 144 | _tex_coords.noalias() = coords; 145 | _tex_faces.noalias() = tri_faces; 146 | _tex_to_vert = util::make_uv_to_vert_map(coords.rows(), faces, tri_faces); 147 | shading_type = ShadingType::texture; 148 | return *this; 149 | } 150 | 151 | Mesh& Mesh::unset_tex_coords() { 152 | _tex_coords.resize(0, 0); 153 | _tex_faces.resize(0, 0); 154 | shading_type = ShadingType::vertex; 155 | return *this; 156 | } 157 | 158 | Mesh& Mesh::set_shininess(float val) { 159 | shininess = val; 160 | return *this; 161 | } 162 | 163 | void Mesh::update(bool force_init) { 164 | static const size_t SCALAR_SZ = sizeof(float); 165 | static const size_t POS_OFFSET = 0; 166 | static const size_t COLOR_OFFSET = 3; 167 | static const size_t NORMALS_OFFSET = 6; 168 | static const size_t VERT_INDICES = data.ColsAtCompileTime; 169 | static const size_t VERT_SZ = VERT_INDICES * SCALAR_SZ; 170 | 171 | if (glfwGetCurrentContext() == nullptr) { 172 | // No OpenGL context is created, exit 173 | return; 174 | } 175 | 176 | // Auto normals 177 | if (_auto_normals) { 178 | util::estimate_normals(verts_pos(), faces, data.rightCols<3>()); 179 | } 180 | 181 | if (!force_init && ~VAO) { 182 | // Already initialized, load any new textures 183 | for (auto& tex_vec : textures) { 184 | for (auto& tex : tex_vec) { 185 | if (!~tex.id) tex.load(); 186 | } 187 | } 188 | } else { 189 | // Re-generate textures and buffers 190 | for (auto& tex_vec : textures) { 191 | for (auto& tex : tex_vec) { 192 | tex.id = -1; 193 | tex.load(); 194 | } 195 | } 196 | blank_tex_id = -1; 197 | 198 | // create buffers/arrays 199 | glGenVertexArrays(1, &VAO); 200 | glGenBuffers(1, &VBO); 201 | glGenBuffers(1, &EBO); 202 | } 203 | 204 | // Figure out buffer sizes 205 | Eigen::Index n_verts, n_faces; 206 | float* vert_data_ptr; 207 | Index* face_data_ptr; 208 | if (_tex_coords.rows()) { 209 | n_verts = _tex_coords.rows(); 210 | n_faces = _tex_faces.rows(); 211 | _data_tex.resize(n_verts, data.ColsAtCompileTime); 212 | // Convert to texture indexing data -> data_tex 213 | for (size_t i = 0; i < n_verts; ++i) { 214 | _data_tex.block<1, 3>(i, POS_OFFSET).noalias() = 215 | data.block<1, 3>(_tex_to_vert[i], POS_OFFSET); 216 | _data_tex.block<1, 3>(i, NORMALS_OFFSET).noalias() = 217 | data.block<1, 3>(_tex_to_vert[i], NORMALS_OFFSET); 218 | } 219 | _data_tex.middleCols<2>(COLOR_OFFSET).noalias() = _tex_coords; 220 | vert_data_ptr = _data_tex.data(); 221 | face_data_ptr = _tex_faces.data(); 222 | } else { 223 | n_verts = data.rows(); 224 | n_faces = faces.rows(); 225 | vert_data_ptr = data.data(); 226 | face_data_ptr = faces.data(); 227 | } 228 | const size_t BUF_SZ = n_verts * data.ColsAtCompileTime * SCALAR_SZ; 229 | const size_t INDEX_SZ = n_faces * faces.ColsAtCompileTime * SCALAR_SZ; 230 | 231 | glBindVertexArray(VAO); 232 | // load data into vertex buffers 233 | glBindBuffer(GL_ARRAY_BUFFER, VBO); 234 | glBufferData(GL_ARRAY_BUFFER, BUF_SZ, (GLvoid*)vert_data_ptr, 235 | GL_STATIC_DRAW); 236 | 237 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 238 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_SZ, (GLvoid*)face_data_ptr, 239 | GL_STATIC_DRAW); 240 | 241 | // set the vertex attribute pointers 242 | // vertex positions 243 | glEnableVertexAttribArray(0); 244 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VERT_SZ, 245 | (GLvoid*)(POS_OFFSET * SCALAR_SZ)); 246 | // vertex texture coords 247 | glEnableVertexAttribArray(1); 248 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, VERT_SZ, 249 | (GLvoid*)(COLOR_OFFSET * SCALAR_SZ)); 250 | // vertex normals 251 | glEnableVertexAttribArray(2); 252 | glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, VERT_SZ, 253 | (GLvoid*)(NORMALS_OFFSET * SCALAR_SZ)); 254 | glBindVertexArray(0); 255 | } 256 | 257 | void Mesh::free_bufs() { 258 | if (~VAO) glDeleteVertexArrays(1, &VAO); 259 | if (~VBO) glDeleteBuffers(1, &VBO); 260 | if (~EBO) glDeleteBuffers(1, &EBO); 261 | if (~blank_tex_id) glDeleteTextures(1, &blank_tex_id); 262 | } 263 | 264 | void Mesh::gen_blank_texture() { 265 | if (~blank_tex_id) return; 266 | glGenTextures(1, &blank_tex_id); 267 | glBindTexture(GL_TEXTURE_2D, blank_tex_id); 268 | 269 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 270 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 271 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 272 | Vector3f grey(0.7f, 0.7f, 0.7f); 273 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 274 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_FLOAT, 275 | grey.data()); 276 | glGenerateMipmap(GL_TEXTURE_2D); 277 | } 278 | 279 | Mesh Mesh::Triangle(const Eigen::Ref& a, 280 | const Eigen::Ref& b, 281 | const Eigen::Ref& c) { 282 | Vector3f n = (b - a).cross(c - b); 283 | Mesh m(3); 284 | m.data << a[0], a[1], a[2], 0.f, 0.f, 0.f, n[0], n[1], n[2], b[0], b[1], 285 | b[2], 0.f, 1.f, 0.f, n[0], n[1], n[2], c[0], c[1], c[2], 1.f, 1.f, 0.f, 286 | n[0], n[1], n[2]; 287 | m.shading_type = ShadingType::texture; 288 | return m; 289 | } 290 | 291 | Mesh Mesh::Square() { 292 | Mesh m(4, 2); 293 | m.faces << 0, 3, 1, 1, 3, 2; 294 | m.data << 0.5, 0.5, 0.f, 1.0f, 1.0f, 0.f, 0.0f, 0.0f, 1.0f, 0.5, -0.5, 0.f, 295 | 1.0f, 0.0f, 0.f, 0.0f, 0.0f, 1.0f, -0.5, -0.5, 0.f, 0.0f, 0.0f, 0.f, 296 | 0.0f, 0.0f, 1.0f, -0.5, 0.5, 0.f, 0.0f, 1.0f, 0.f, 0.0f, 0.0f, 1.0f; 297 | m.shading_type = ShadingType::texture; 298 | return m; 299 | } 300 | 301 | Mesh Mesh::Cube() { 302 | Mesh m(36); 303 | m.data << 304 | // positions // uv coords // normals 305 | // back 306 | -0.5, 307 | -0.5, -0.5, 0.0f, 0.0f, 0.f, 0.0f, 0.0f, -1.0f, 0.5, 0.5, -0.5, 1.0f, 308 | 1.0f, 0.f, 0.0f, 0.0f, -1.0f, 0.5, -0.5, -0.5, 1.0f, 0.0f, 0.f, 0.0f, 309 | 0.0f, -1.0f, 0.5, 0.5, -0.5, 1.0f, 1.0f, 0.f, 0.0f, 0.0f, -1.0f, -0.5, 310 | -0.5, -0.5, 0.0f, 0.0f, 0.f, 0.0f, 0.0f, -1.0f, -0.5, 0.5, -0.5, 0.0f, 311 | 1.0f, 0.f, 0.0f, 0.0f, -1.0f, 312 | 313 | // front 314 | -0.5, -0.5, 0.5, 0.0f, 0.0f, 0.f, 0.0f, 0.0f, 1.0f, 0.5, -0.5, 0.5, 315 | 1.0f, 0.0f, 0.f, 0.0f, 0.0f, 1.0f, 0.5, 0.5, 0.5, 1.0f, 1.0f, 0.f, 0.0f, 316 | 0.0f, 1.0f, 0.5, 0.5, 0.5, 1.0f, 1.0f, 0.f, 0.0f, 0.0f, 1.0f, -0.5, 0.5, 317 | 0.5, 0.0f, 1.0f, 0.f, 0.0f, 0.0f, 1.0f, -0.5, -0.5, 0.5, 0.0f, 0.0f, 318 | 0.f, 0.0f, 0.0f, 1.0f, 319 | 320 | // left 321 | -0.5, 0.5, 0.5, 1.0f, 0.0f, 0.f, -1.0f, 0.0f, 0.0f, -0.5, 0.5, -0.5, 322 | 1.0f, 1.0f, 0.f, -1.0f, 0.0f, 0.0f, -0.5, -0.5, -0.5, 0.0f, 1.0f, 0.f, 323 | -1.0f, 0.0f, 0.0f, -0.5, -0.5, -0.5, 0.0f, 1.0f, 0.f, -1.0f, 0.0f, 0.0f, 324 | -0.5, -0.5, 0.5, 0.0f, 0.0f, 0.f, -1.0f, 0.0f, 0.0f, -0.5, 0.5, 0.5, 325 | 1.0f, 0.0f, 0.f, -1.0f, 0.0f, 0.0f, 326 | 327 | // right 328 | 0.5, 0.5, 0.5, 1.0f, 0.0f, 0.f, 1.0f, 0.0f, 0.0f, 0.5, -0.5, -0.5, 0.0f, 329 | 1.0f, 0.f, 1.0f, 0.0f, 0.0f, 0.5, 0.5, -0.5, 1.0f, 1.0f, 0.f, 1.0f, 330 | 0.0f, 0.0f, 0.5, -0.5, -0.5, 0.0f, 1.0f, 0.f, 1.0f, 0.0f, 0.0f, 0.5, 331 | 0.5, 0.5, 1.0f, 0.0f, 0.f, 1.0f, 0.0f, 0.0f, 0.5, -0.5, 0.5, 0.0f, 0.0f, 332 | 0.f, 1.0f, 0.0f, 0.0f, 333 | 334 | // bottom 335 | -0.5, -0.5, -0.5, 0.0f, 1.0f, 0.f, 0.0f, -1.0f, 0.0f, 0.5, -0.5, -0.5, 336 | 1.0f, 1.0f, 0.f, 0.0f, -1.0f, 0.0f, 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.f, 337 | 0.0f, -1.0f, 0.0f, 0.5, -0.5, 0.5, 1.0f, 0.0f, 0.f, 0.0f, -1.0f, 0.0f, 338 | -0.5, -0.5, 0.5, 0.0f, 0.0f, 0.f, 0.0f, -1.0f, 0.0f, -0.5, -0.5, -0.5, 339 | 0.0f, 1.0f, 0.f, 0.0f, -1.0f, 0.0f, 340 | 341 | // top 342 | -0.5, 0.5, -0.5, 0.0f, 1.0f, 0.f, 0.0f, 1.0f, 0.0f, 0.5, 0.5, 0.5, 1.0f, 343 | 0.0f, 0.f, 0.0f, 1.0f, 0.0f, 0.5, 0.5, -0.5, 1.0f, 1.0f, 0.f, 0.0f, 344 | 1.0f, 0.0f, 0.5, 0.5, 0.5, 1.0f, 0.0f, 0.f, 0.0f, 1.0f, 0.0f, -0.5, 0.5, 345 | -0.5, 0.0f, 1.0f, 0.f, 0.0f, 1.0f, 0.0f, -0.5, 0.5, 0.5, 0.0f, 0.0f, 346 | 0.f, 0.0f, 1.0f, 0.0f; 347 | m.shading_type = ShadingType::texture; 348 | return m; 349 | } 350 | 351 | Mesh Mesh::Sphere(int rings, int sectors) { 352 | Mesh m(rings * sectors, (rings - 1) * sectors * 2); 353 | const float R = M_PI / (float)(rings - 1); 354 | const float S = 2 * M_PI / (float)sectors; 355 | size_t vid = 0; 356 | for (int r = 0; r < rings; r++) { 357 | for (int s = 0; s < sectors; s++) { 358 | const float y = sin(-0.5f * M_PI + r * R); 359 | const float x = cos(s * S) * sin(r * R); 360 | const float z = sin(s * S) * sin(r * R); 361 | m.data.row(vid++) << x, y, z, s * S, r * R, 0.f, x, y, z; 362 | } 363 | } 364 | size_t fid = 0; 365 | for (int r = 0; r < rings - 1; r++) { 366 | const int nx_r = r + 1; 367 | for (int s = 0; s < sectors; s++) { 368 | const int nx_s = (s == sectors - 1) ? 0 : s + 1; 369 | m.faces.row(fid++) << r * sectors + nx_s, r * sectors + s, 370 | nx_r * sectors + s; 371 | m.faces.row(fid++) << nx_r * sectors + s, nx_r * sectors + nx_s, 372 | r * sectors + nx_s; 373 | } 374 | } 375 | _MESHVIEW_ASSERT_EQ(vid, m.data.rows()); 376 | _MESHVIEW_ASSERT_EQ(fid, m.faces.rows()); 377 | m.shading_type = ShadingType::texture; 378 | return m; 379 | } 380 | 381 | void Mesh::save_basic_obj(const std::string& path) const { 382 | std::ofstream ofs(path); 383 | for (int i = 0; i < data.rows(); ++i) { 384 | ofs << "v"; 385 | // Transform point 386 | Vector4f v = 387 | transform * data.block<1, 3>(i, 0).transpose().homogeneous(); 388 | for (int j = 0; j < 3; ++j) { 389 | ofs << " " << v(j); 390 | } 391 | if (shading_type == ShadingType::vertex) { 392 | for (int j = 3; j < 6; ++j) { 393 | ofs << " " << data(i, j); 394 | } 395 | } 396 | ofs << "\n"; 397 | } 398 | for (int i = 0; i < faces.rows(); ++i) { 399 | ofs << "f"; 400 | for (int j = 0; j < 3; ++j) { 401 | ofs << " " << faces(i, j) + 1; 402 | } 403 | ofs << "\n"; 404 | } 405 | } 406 | 407 | void Mesh::load_basic_obj(const std::string& path) { 408 | std::ifstream ifs(path); 409 | std::string line; 410 | std::vector tmp_pos, tmp_rgb; 411 | std::vector tmp_faces; 412 | size_t attrs_per_vert = 0; 413 | while (std::getline(ifs, line)) { 414 | if (line.size() < 2 || line[1] != ' ') continue; 415 | if (line[0] == 'v') { 416 | std::stringstream ss(line.substr(2)); 417 | double tmp; 418 | size_t cnt = 0; 419 | while (ss >> tmp) { 420 | if (cnt >= 3) { 421 | tmp_rgb.push_back(tmp); 422 | } else { 423 | tmp_pos.push_back(tmp); 424 | } 425 | ++cnt; 426 | } 427 | if (attrs_per_vert) { 428 | _MESHVIEW_ASSERT_EQ(cnt, attrs_per_vert); 429 | } else { 430 | _MESHVIEW_ASSERT(cnt == 3 || cnt == 6); 431 | attrs_per_vert = cnt; 432 | } 433 | } else if (line[0] == 'f') { 434 | size_t i = 2, j = 0; 435 | while (i < line.size() && std::isspace(i)) ++i; 436 | std::string num; 437 | for (; i < line.size(); ++i) { 438 | if (line[i] == '/') { 439 | ++j; 440 | } else if (std::isspace(line[i])) { 441 | tmp_faces.push_back((Index)std::atoll(num.c_str()) - 1); 442 | j = 0; 443 | num.clear(); 444 | } else if (j == 0) { 445 | num.push_back(line[i]); 446 | } 447 | } 448 | if (num.size()) { 449 | tmp_faces.push_back((Index)std::atoll(num.c_str()) - 1); 450 | } 451 | } 452 | } 453 | if (attrs_per_vert == 6) { 454 | shading_type = ShadingType::vertex; 455 | _MESHVIEW_ASSERT_EQ(tmp_rgb.size(), tmp_pos.size()); 456 | _MESHVIEW_ASSERT_EQ(tmp_rgb.size() % 3, 0); 457 | } else { 458 | shading_type = ShadingType::texture; 459 | _MESHVIEW_ASSERT_EQ(tmp_rgb.size(), 0); 460 | } 461 | _MESHVIEW_ASSERT_EQ(tmp_faces.size() % 3, 0); 462 | data.resize(tmp_pos.size() / 3, data.ColsAtCompileTime); 463 | 464 | data.leftCols<3>().noalias() = 465 | Eigen::Map(tmp_pos.data(), data.rows(), 3); 466 | if (tmp_rgb.size()) { 467 | data.middleCols<3>(3).noalias() = 468 | Eigen::Map(tmp_rgb.data(), data.rows(), 3); 469 | } 470 | if (tmp_faces.size()) { 471 | faces.resize(tmp_faces.size() / 3, faces.ColsAtCompileTime); 472 | faces.noalias() = 473 | Eigen::Map(tmp_faces.data(), faces.rows(), 3); 474 | } else { 475 | faces.resize(data.rows() / 3, faces.ColsAtCompileTime); 476 | for (Index i = 0; i < (Index)data.rows(); ++i) { 477 | faces.data()[i] = i; 478 | } 479 | } 480 | transform.setIdentity(); 481 | } 482 | 483 | // *** PointCloud *** 484 | PointCloud::PointCloud(size_t num_verts) : VAO((Index)-1) { resize(num_verts); } 485 | PointCloud::PointCloud(const Eigen::Ref& pos, 486 | const Eigen::Ref& rgb) 487 | : PointCloud(pos.rows()) { 488 | if (!pos.rows() || (rgb.rows() && pos.rows() != rgb.rows())) { 489 | std::cerr << "Invalid meshview::PointCloud construction: " 490 | "pos cannot be empty and pos, rgb should have identical # " 491 | "rows\n"; 492 | return; 493 | } 494 | verts_pos().noalias() = pos; 495 | if (rgb.rows()) { 496 | verts_rgb().noalias() = rgb; 497 | } 498 | } 499 | PointCloud::PointCloud(const Eigen::Ref& pos, float r, float g, 500 | float b) 501 | : PointCloud(pos.rows()) { 502 | verts_pos().noalias() = pos; 503 | verts_rgb().rowwise() = Eigen::RowVector3f(r, g, b); 504 | } 505 | PointCloud::~PointCloud() { free_bufs(); } 506 | 507 | void PointCloud::resize(size_t num_verts) { 508 | data.resize(num_verts, data.ColsAtCompileTime); 509 | transform.setIdentity(); 510 | } 511 | 512 | void PointCloud::update(bool force_init) { 513 | static const size_t SCALAR_SZ = sizeof(float); 514 | static const size_t POS_OFFSET = 0; 515 | static const size_t RGB_OFFSET = 3 * SCALAR_SZ; 516 | static const size_t VERT_INDICES = data.ColsAtCompileTime; 517 | static const size_t VERT_SZ = VERT_INDICES * SCALAR_SZ; 518 | 519 | if (glfwGetCurrentContext() == nullptr) { 520 | // No OpenGL context is created, exit 521 | return; 522 | } 523 | 524 | const size_t BUF_SZ = data.size() * SCALAR_SZ; 525 | 526 | // Already initialized 527 | if (force_init || !~VAO) { 528 | // Create buffers/arrays 529 | glGenVertexArrays(1, &VAO); 530 | glGenBuffers(1, &VBO); 531 | } 532 | glBindVertexArray(VAO); 533 | // load data into vertex buffers 534 | glBindBuffer(GL_ARRAY_BUFFER, VBO); 535 | // A great thing about structs is that their memory layout is sequential 536 | // for all its items. The effect is that we can simply pass a pointer to 537 | // the struct and it translates perfectly to a glm::vec3/2 array which 538 | // again translates to 3/2 floats which translates to a byte array. 539 | glBufferData(GL_ARRAY_BUFFER, BUF_SZ, (GLvoid*)data.data(), GL_STATIC_DRAW); 540 | 541 | // set the vertex attribute pointers 542 | // vertex positions 543 | glEnableVertexAttribArray(0); 544 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VERT_SZ, 545 | (GLvoid*)POS_OFFSET); 546 | // vertex color 547 | glEnableVertexAttribArray(1); 548 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, VERT_SZ, 549 | (GLvoid*)RGB_OFFSET); 550 | glBindVertexArray(0); 551 | } 552 | 553 | void PointCloud::draw(Index shader_id, const Camera& camera) { 554 | if (!enabled) return; 555 | if (!~VAO) { 556 | std::cerr << "ERROR: Please call meshview::PointCloud::update() before " 557 | "PointCloud::draw()\n"; 558 | return; 559 | } 560 | internal::Shader shader(shader_id); 561 | 562 | // Set point size 563 | glPointSize(point_size); 564 | 565 | // Set space transform matrices 566 | shader_set_transform_matrices(shader, camera, transform); 567 | 568 | // Draw mesh 569 | glBindVertexArray(VAO); 570 | glDrawArrays(lines ? GL_LINES : GL_POINTS, 0, (GLsizei)data.rows()); 571 | glBindVertexArray(0); 572 | 573 | // Always good practice to set everything back to defaults once 574 | // configured. 575 | glActiveTexture(GL_TEXTURE0); 576 | } 577 | 578 | void PointCloud::free_bufs() { 579 | if (~VAO) glDeleteVertexArrays(1, &VAO); 580 | if (~VBO) glDeleteBuffers(1, &VBO); 581 | } 582 | 583 | PointCloud PointCloud::Line(const Eigen::Ref& a, 584 | const Eigen::Ref& b, 585 | const Eigen::Ref& color) { 586 | PointCloud tmp(2); 587 | tmp.verts_pos().topRows<1>().noalias() = a; 588 | tmp.verts_pos().bottomRows<1>().noalias() = b; 589 | tmp.verts_rgb().rowwise() = color.transpose(); 590 | tmp.draw_lines(); 591 | return tmp; 592 | } 593 | 594 | // *** Shared *** 595 | // Define identical function for both mesh, pointcloud classes 596 | #define BOTH_MESH_AND_POINTCLOUD(fbody) \ 597 | Mesh& Mesh::fbody PointCloud& PointCloud::fbody 598 | 599 | BOTH_MESH_AND_POINTCLOUD(translate(const Eigen::Ref& vec) { 600 | (transform.topRightCorner<3, 1>() += vec); 601 | return *this; 602 | }) 603 | 604 | BOTH_MESH_AND_POINTCLOUD( 605 | set_translation(const Eigen::Ref& vec) { 606 | (transform.topRightCorner<3, 1>() = vec); 607 | return *this; 608 | }) 609 | 610 | BOTH_MESH_AND_POINTCLOUD(rotate(const Eigen::Ref& mat) { 611 | (transform.topLeftCorner<3, 3>() = mat * transform.topLeftCorner<3, 3>()); 612 | return *this; 613 | }) 614 | 615 | BOTH_MESH_AND_POINTCLOUD(scale(const Eigen::Ref& vec) { 616 | (transform.topLeftCorner<3, 3>().array().colwise() *= vec.array()); 617 | return *this; 618 | }) 619 | 620 | BOTH_MESH_AND_POINTCLOUD(scale(float val) { 621 | (transform.topLeftCorner<3, 3>().array() *= val); 622 | return *this; 623 | }) 624 | 625 | BOTH_MESH_AND_POINTCLOUD( 626 | apply_transform(const Eigen::Ref& mat) { 627 | transform = mat * transform; 628 | return *this; 629 | }) 630 | 631 | BOTH_MESH_AND_POINTCLOUD(set_transform(const Eigen::Ref& mat) { 632 | transform = mat; 633 | return *this; 634 | }) 635 | 636 | BOTH_MESH_AND_POINTCLOUD(enable(bool val) { 637 | enabled = val; 638 | return *this; 639 | }) 640 | 641 | } // namespace meshview 642 | -------------------------------------------------------------------------------- /src/shader.cpp: -------------------------------------------------------------------------------- 1 | // Copied & adapted from learnopengl.com 2 | #include "meshview/internal/shader.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace meshview { 10 | namespace internal { 11 | 12 | namespace { 13 | 14 | void check_compile_errors(GLuint shader, const std::string& type) { 15 | GLint success; 16 | GLchar infoLog[1024]; 17 | if(type != "PROGRAM") { 18 | glGetShaderiv(shader, GL_COMPILE_STATUS, &success); 19 | if(!success) { 20 | glGetShaderInfoLog(shader, 1024, NULL, infoLog); 21 | std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << 22 | "\n" << infoLog << 23 | "\n -- --------------------------------------------------- " << std::endl; 24 | } 25 | } 26 | else { 27 | glGetProgramiv(shader, GL_LINK_STATUS, &success); 28 | if(!success) { 29 | glGetProgramInfoLog(shader, 1024, NULL, infoLog); 30 | std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << 31 | "\n" << infoLog << 32 | "\n -- --------------------------------------------------- -- " << std::endl; 33 | } 34 | } 35 | } 36 | 37 | } // namespace 38 | 39 | Shader::Shader(Index id) : id(id) { 40 | } 41 | 42 | Shader::Shader(const std::string& vertex_code, 43 | const std::string& fragment_code, 44 | const std::string& geometry_code) { 45 | const char* v_shader_code = vertex_code.c_str(); 46 | const char * f_shader_code = fragment_code.c_str(); 47 | Index vertex, fragment; 48 | // Vertex shader 49 | vertex = glCreateShader(GL_VERTEX_SHADER); 50 | glShaderSource(vertex, 1, &v_shader_code, NULL); 51 | glCompileShader(vertex); 52 | check_compile_errors(vertex, "VERTEX"); 53 | // Fragment Shader 54 | fragment = glCreateShader(GL_FRAGMENT_SHADER); 55 | glShaderSource(fragment, 1, &f_shader_code, NULL); 56 | glCompileShader(fragment); 57 | check_compile_errors(fragment, "FRAGMENT"); 58 | // If geometry shader is given, compile geometry shader 59 | Index geometry; 60 | if(geometry_code.size()) { 61 | const char * g_shader_code = geometry_code.c_str(); 62 | geometry = glCreateShader(GL_GEOMETRY_SHADER); 63 | glShaderSource(geometry, 1, &g_shader_code, NULL); 64 | glCompileShader(geometry); 65 | check_compile_errors(geometry, "GEOMETRY"); 66 | } 67 | // Shader program 68 | id = glCreateProgram(); 69 | glAttachShader(id, vertex); 70 | glAttachShader(id, fragment); 71 | if(geometry_code.size()) 72 | glAttachShader(id, geometry); 73 | glLinkProgram(id); 74 | check_compile_errors(id, "PROGRAM"); 75 | // Delete the shaders as they're linked into our program now and no longer necessery 76 | glDeleteShader(vertex); 77 | glDeleteShader(fragment); 78 | if(geometry_code.size()) glDeleteShader(geometry); 79 | } 80 | 81 | void Shader::use() { 82 | if (id == (Index)-1) { 83 | std::cerr << "Shader is not initialized\n"; 84 | return; 85 | } 86 | glUseProgram(id); 87 | } 88 | 89 | void Shader::set_bool(const std::string &name, bool value) const { 90 | glUniform1i(glGetUniformLocation(id, name.c_str()), (int)value); 91 | } 92 | 93 | void Shader::set_int(const std::string &name, int value) const { 94 | glUniform1i(glGetUniformLocation(id, name.c_str()), value); 95 | } 96 | void Shader::set_float(const std::string &name, float value) const { 97 | glUniform1f(glGetUniformLocation(id, name.c_str()), value); 98 | } 99 | void Shader::set_vec2(const std::string &name, float x, float y) const { 100 | glUniform2f(glGetUniformLocation(id, name.c_str()), x, y); 101 | } 102 | void Shader::set_vec3(const std::string &name, float x, float y, float z) const { 103 | glUniform3f(glGetUniformLocation(id, name.c_str()), x, y, z); 104 | } 105 | void Shader::set_vec4(const std::string &name, float x, float y, float z, float w) { 106 | glUniform4f(glGetUniformLocation(id, name.c_str()), x, y, z, w); 107 | } 108 | 109 | void Shader::set_vec2(const std::string &name, const Eigen::Ref &value) const { 110 | glUniform2fv(glGetUniformLocation(id, name.c_str()), 1, value.data()); 111 | } 112 | void Shader::set_vec3(const std::string &name, const Eigen::Ref &value) const { 113 | glUniform3fv(glGetUniformLocation(id, name.c_str()), 1, value.data()); 114 | } 115 | void Shader::set_vec4(const std::string &name, const Eigen::Ref &value) const { 116 | glUniform4fv(glGetUniformLocation(id, name.c_str()), 1, value.data()); 117 | } 118 | void Shader::set_mat2(const std::string &name, const Eigen::Ref &mat) const { 119 | glUniformMatrix2fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, mat.data()); 120 | } 121 | void Shader::set_mat3(const std::string &name, const Eigen::Ref &mat) const { 122 | glUniformMatrix3fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, mat.data()); 123 | } 124 | void Shader::set_mat4(const std::string &name, const Eigen::Ref &mat) const { 125 | glUniformMatrix4fv(glGetUniformLocation(id, name.c_str()), 1, GL_FALSE, mat.data()); 126 | } 127 | } // namespace internal 128 | } // namespace meshview 129 | 130 | #include 131 | #include 132 | #include 133 | -------------------------------------------------------------------------------- /src/texture.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/meshview.hpp" 2 | 3 | #include 4 | #include 5 | #include "stb_image.h" 6 | #include "meshview/util.hpp" 7 | #include "meshview/internal/assert.hpp" 8 | 9 | namespace meshview { 10 | 11 | // *** Texture *** 12 | Texture::Texture(const std::string& path, bool flip) 13 | : path(path), fallback_color(/*pink*/ 1.f, 0.75f, 0.8f), flip(flip) { 14 | } 15 | 16 | Texture::Texture(float r, float g, float b) : fallback_color(r, g, b) { } 17 | Texture::Texture(const Eigen::Ref& im, int n_channels) : im_data(im), 18 | n_channels(n_channels), fallback_color(/*pink*/ 1.f, 0.75f, 0.8f), flip(false) { 19 | _MESHVIEW_ASSERT(n_channels == 1 || n_channels == 3 || n_channels == 4); 20 | _MESHVIEW_ASSERT_EQ(im.cols() % n_channels, 0); 21 | } 22 | 23 | Texture::~Texture() { 24 | free_bufs(); 25 | } 26 | 27 | void Texture::free_bufs() { 28 | if (~id) glDeleteTextures(1, &id); 29 | id = -1; 30 | } 31 | 32 | void Texture::load() { 33 | if (!~id) 34 | glGenTextures(1, &id); 35 | glBindTexture(GL_TEXTURE_2D, id); 36 | 37 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 38 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 39 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 40 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 41 | 42 | auto gl_load_mipmap = [](int width, int height, int chnls, void* data, 43 | int dtype) { 44 | _MESHVIEW_ASSERT(chnls == 1 || chnls == 3 || chnls == 4); 45 | GLenum format; 46 | if (chnls == 1) 47 | format = GL_RED; 48 | else if (chnls == 3) 49 | format = GL_RGB; 50 | else //if (chnls == 4) 51 | format = GL_RGBA; 52 | glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 53 | 0, format, dtype, data); 54 | glGenerateMipmap(GL_TEXTURE_2D); 55 | }; 56 | 57 | bool success = false; 58 | if (im_data.rows()) { 59 | // From memory 60 | gl_load_mipmap(im_data.cols() / n_channels, im_data.rows(), n_channels, 61 | (void*) im_data.data(), GL_FLOAT); 62 | success = true; 63 | } else if (path.size()) { 64 | // From file 65 | stbi_set_flip_vertically_on_load(flip); 66 | int width, height, chnls; 67 | uint8_t * data = stbi_load(path.c_str(), &width, &height, &chnls, 0); 68 | if (data) { 69 | im_data.resize(height, width * chnls); 70 | im_data.noalias() = Eigen::Map(data, height, width * chnls) 71 | .cast() / 255.f; 72 | n_channels = chnls; 73 | gl_load_mipmap(width, height, chnls, (void*) data, GL_UNSIGNED_BYTE); 74 | stbi_image_free(data); 75 | success = true; 76 | } else { 77 | std::cerr << "Failed to load texture " << path << ", using fallback color\n"; 78 | } 79 | } 80 | if (!success) { 81 | gl_load_mipmap(1, 1, 3, fallback_color.data(), GL_FLOAT); 82 | } 83 | } 84 | 85 | } // namespace 86 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/util.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "meshview/common.hpp" 8 | #include "meshview/internal/assert.hpp" 9 | 10 | namespace meshview { 11 | namespace util { 12 | 13 | Matrix4f persp(float xscale, float yscale, float z_near, float z_far) { 14 | Matrix4f m; 15 | m << xscale, 0.f, 0.f, 0.f, 0.f, yscale, 0.f, 0.f, 0.f, 0.f, 16 | -(z_far + z_near) / (z_far - z_near), 17 | -2.f * z_near * z_far / (z_far - z_near), 0.f, 0.f, -1.f, 0.f; 18 | // 0.f, 0.f, z_far / (z_near - z_far), -(z_far * z_near) / (z_far - z_near), 19 | // 0.f, 0.f, -1.f, 0.f; 20 | return m; 21 | } 22 | 23 | Matrix4f look_at(const Eigen::Ref& pos, 24 | const Eigen::Ref& fw, 25 | const Eigen::Ref& up) { 26 | Matrix4f m; 27 | m.topLeftCorner<1, 3>() = fw.cross(up).transpose(); 28 | m.block<1, 3>(1, 0) = up.transpose(); 29 | m.block<1, 3>(2, 0) = -fw.transpose(); 30 | m.bottomLeftCorner<1, 3>().setZero(); 31 | m(3, 3) = 1.f; 32 | m.topRightCorner<3, 1>() = -m.topLeftCorner<3, 3>() * pos; 33 | return m; 34 | } 35 | 36 | void estimate_normals(const Eigen::Ref& verts, 37 | const Eigen::Ref& faces, 38 | Eigen::Ref out) { 39 | if (faces.rows() == 0) { 40 | // Defer to 'no-element-buffer' version 41 | estimate_normals(verts, out); 42 | return; 43 | } 44 | Vector face_cnt(verts.rows()); 45 | face_cnt.setZero(); 46 | 47 | out.setZero(); 48 | 49 | Eigen::RowVector3f face_normal; 50 | for (int i = 0; i < faces.rows(); ++i) { 51 | face_normal = 52 | (verts.row(faces(i, 1)) - verts.row(faces(i, 0))) 53 | .cross(verts.row(faces(i, 2)) - verts.row(faces(i, 1))) 54 | .normalized(); 55 | for (int j = 0; j < 3; ++j) { 56 | out.row(faces(i, j)) += face_normal; 57 | face_cnt[faces(i, j)] += 1.f; 58 | } 59 | } 60 | out.array().colwise() /= face_cnt.array(); 61 | } 62 | 63 | void estimate_normals(const Eigen::Ref& verts, 64 | Eigen::Ref out) { 65 | Vector face_cnt(verts.rows()); 66 | face_cnt.setZero(); 67 | out.setZero(); 68 | Vector3f face_normal; 69 | for (int i = 0; i < verts.rows(); i += 3) { 70 | face_cnt.segment<3>(i).array() += 1.f; 71 | out.middleRows<3>(i).rowwise() += 72 | (verts.row(i + 1) - verts.row(i)) 73 | .cross(verts.row(i + 2) - verts.row(i + 1)) 74 | .normalized(); 75 | } 76 | out.array().colwise() /= face_cnt.array(); 77 | } 78 | 79 | Eigen::Matrix make_uv_to_vert_map( 80 | size_t num_uv_verts, const Eigen::Ref& tri_faces, 81 | const Eigen::Ref& uv_tri_faces) { 82 | _MESHVIEW_ASSERT_EQ(tri_faces.rows(), uv_tri_faces.rows()); 83 | Eigen::Matrix result(num_uv_verts); 84 | result.setConstant(-1); 85 | for (size_t i = 0; i < uv_tri_faces.rows(); ++i) { 86 | for (size_t j = 0; j < 3; ++j) { 87 | result[uv_tri_faces(i, j)] = tri_faces(i, j); 88 | } 89 | } 90 | // Each uv vertex must be matched to some vertex 91 | for (size_t i = 0; i < num_uv_verts; ++i) { 92 | _MESHVIEW_ASSERT_NE(result[i], (Index)-1); 93 | } 94 | return result; 95 | } 96 | 97 | } // namespace util 98 | } // namespace meshview 99 | -------------------------------------------------------------------------------- /src/viewer.cpp: -------------------------------------------------------------------------------- 1 | #include "meshview/meshview.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "meshview/util.hpp" 10 | #include "meshview/internal/shader.hpp" 11 | // Inlined shader code 12 | #include "meshview/internal/shader_inline.hpp" 13 | 14 | #ifdef MESHVIEW_IMGUI 15 | #include "meshview/imgui/imgui_impl_opengl3.h" 16 | #include "meshview/imgui/imgui_impl_glfw.h" 17 | #endif 18 | 19 | namespace meshview { 20 | namespace { 21 | // Axes data 22 | const float AXIS_LEN = 0.5f; 23 | const float axes_verts[] = {0.0f, 0.0f, 0.0f, AXIS_LEN, 0.0f, 0.0f, 24 | 0.0f, 0.0f, 0.0f, 0.0f, AXIS_LEN, 0.0f, 25 | 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, AXIS_LEN}; 26 | const float axes_rgb[] = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 27 | 0.0f, 1.0f, 0.0f, 0.2f, 0.2f, 1.0f, 0.2f, 0.2f, 1.0f}; 28 | 29 | void error_callback(int error, const char* description) { 30 | std::cerr << description << "\n"; 31 | } 32 | 33 | void win_key_callback(GLFWwindow* window, int key, int scancode, int action, 34 | int mods) { 35 | meshview::Viewer& viewer = 36 | *reinterpret_cast(glfwGetWindowUserPointer(window)); 37 | if (viewer.on_key && 38 | !viewer.on_key(key, (meshview::input::Action)action, mods)) 39 | return; 40 | 41 | #ifdef MESHVIEW_IMGUI 42 | ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods); 43 | if (ImGui::GetIO().WantCaptureKeyboard) return; 44 | #endif 45 | 46 | if (action == GLFW_PRESS) { 47 | switch (key) { 48 | case GLFW_KEY_ESCAPE: 49 | case 'Q': 50 | glfwSetWindowShouldClose(window, GL_TRUE); 51 | break; 52 | case 'Z': 53 | viewer.camera.reset_view(); 54 | break; 55 | case 'O': 56 | viewer.camera.ortho = !viewer.camera.ortho; 57 | viewer.camera.update_proj(); 58 | break; 59 | case 'W': 60 | viewer.wireframe = !viewer.wireframe; 61 | break; 62 | case 'C': 63 | viewer.cull_face = !viewer.cull_face; 64 | break; 65 | case 'A': 66 | viewer.draw_axes = !viewer.draw_axes; 67 | break; 68 | case 'M': 69 | if (glfwGetWindowAttrib(window, GLFW_MAXIMIZED) == GLFW_TRUE) { 70 | glfwRestoreWindow(window); 71 | } else { 72 | glfwMaximizeWindow(window); 73 | } 74 | break; 75 | case 'F': { 76 | int* backup = viewer._fullscreen_backup; 77 | if (viewer._fullscreen) { 78 | glfwSetWindowMonitor(window, nullptr, backup[0], backup[1], 79 | backup[2], backup[3], 0); 80 | viewer._fullscreen = false; 81 | } else { 82 | glfwGetWindowPos(window, &backup[0], &backup[1]); 83 | glfwGetWindowSize(window, &backup[2], &backup[3]); 84 | const GLFWvidmode* mode = 85 | glfwGetVideoMode(glfwGetPrimaryMonitor()); 86 | glfwSetWindowMonitor(window, glfwGetPrimaryMonitor(), 0, 0, 87 | mode->width, mode->height, 0); 88 | viewer._fullscreen = true; 89 | } 90 | } break; 91 | case 'H': 92 | std::cout << 93 | R"HELP(Meshview (c) Alex Yu 2020 94 | left click + drag: rotate view 95 | shift + left click + drag: pan view 96 | middle click + drag: pan view (alt) 97 | ctrl + left click + drag: roll view 98 | Z: reset view 99 | O: toggle orthographic 100 | A: toggle axes 101 | W: toggle wireframe 102 | C: toggle backface culling 103 | M: toggle maximize window (may not work on some systems) 104 | F: toggle fullscreen window 105 | )HELP"; 106 | break; 107 | } 108 | } 109 | } 110 | 111 | void win_mouse_button_callback(GLFWwindow* window, int button, int action, 112 | int mods) { 113 | meshview::Viewer& viewer = 114 | *reinterpret_cast(glfwGetWindowUserPointer(window)); 115 | glfwGetCursorPos(window, &viewer._mouse_x, &viewer._mouse_y); 116 | 117 | if (action == GLFW_RELEASE) viewer._mouse_button = -1; 118 | if (viewer.on_mouse_button && 119 | !viewer.on_mouse_button(button, (meshview::input::Action)action, 120 | mods)) { 121 | return; 122 | } 123 | #ifdef MESHVIEW_IMGUI 124 | ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods); 125 | if (ImGui::GetIO().WantCaptureMouse) return; 126 | #endif 127 | if (action == GLFW_PRESS) { 128 | viewer._mouse_button = button; 129 | viewer._mouse_mods = mods; 130 | } 131 | } 132 | 133 | void win_mouse_move_callback(GLFWwindow* window, double x, double y) { 134 | bool prevent_default = false; 135 | 136 | meshview::Viewer& viewer = 137 | *reinterpret_cast(glfwGetWindowUserPointer(window)); 138 | double prex = viewer._mouse_x, prey = viewer._mouse_y; 139 | viewer._mouse_x = x, viewer._mouse_y = y; 140 | if (viewer.on_mouse_move && !viewer.on_mouse_move(x, y)) { 141 | return; 142 | } 143 | if (viewer._mouse_button != -1) { 144 | if ((viewer._mouse_button == GLFW_MOUSE_BUTTON_LEFT && 145 | (viewer._mouse_mods & GLFW_MOD_SHIFT)) || 146 | viewer._mouse_button == GLFW_MOUSE_BUTTON_MIDDLE) { 147 | // Pan 148 | viewer.camera.pan_with_mouse((float)(x - prex), (float)(y - prey)); 149 | } else if (viewer._mouse_button == GLFW_MOUSE_BUTTON_LEFT && 150 | (viewer._mouse_mods & GLFW_MOD_CONTROL)) { 151 | // Roll 152 | viewer.camera.roll_with_mouse((float)(x - prex), (float)(y - prey)); 153 | } else if (viewer._mouse_button == GLFW_MOUSE_BUTTON_LEFT) { 154 | viewer.camera.rotate_with_mouse((float)(x - prex), 155 | (float)(y - prey)); 156 | } 157 | } 158 | } 159 | 160 | void win_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { 161 | meshview::Viewer& viewer = 162 | *reinterpret_cast(glfwGetWindowUserPointer(window)); 163 | if (viewer.on_scroll && !viewer.on_scroll(xoffset, yoffset)) { 164 | return; 165 | } 166 | #ifdef MESHVIEW_IMGUI 167 | ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset); 168 | if (ImGui::GetIO().WantCaptureMouse) return; 169 | #endif 170 | viewer.camera.zoom_with_mouse((float)yoffset); 171 | } 172 | 173 | // Window resize 174 | void win_framebuffer_size_callback(GLFWwindow* window, int width, int height) { 175 | meshview::Viewer& viewer = 176 | *reinterpret_cast(glfwGetWindowUserPointer(window)); 177 | viewer.camera.aspect = (float)width / (float)height; 178 | viewer.camera.update_proj(); 179 | glViewport(0, 0, width, height); 180 | } 181 | } // namespace 182 | 183 | Viewer::Viewer() : _fullscreen(false) { 184 | glfwSetErrorCallback(error_callback); 185 | 186 | if (!glfwInit()) { 187 | std::cerr << "GLFW failed to initialize\n"; 188 | return; 189 | } 190 | 191 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 192 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 193 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 194 | #ifdef __APPLE__ 195 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 196 | #endif 197 | 198 | background.setZero(); 199 | 200 | light_color_ambient.setConstant(0.2f); 201 | light_color_diffuse.setConstant(1.f); 202 | light_color_specular.setConstant(0.25f); 203 | light_pos << 12.f, 10.f, 20.f; 204 | } 205 | 206 | Viewer::~Viewer() { glfwTerminate(); } 207 | 208 | void Viewer::show() { 209 | GLFWwindow* window = 210 | glfwCreateWindow(_width, _height, "meshview", NULL, NULL); 211 | if (!window) { 212 | glfwTerminate(); 213 | std::cerr << "GLFW window creation failed\n"; 214 | return; 215 | } 216 | _window = (void*)window; 217 | 218 | camera.aspect = (float)_width / (float)_height; 219 | camera.update_proj(); 220 | camera.update_view(); 221 | 222 | glfwMakeContextCurrent(window); 223 | 224 | glewExperimental = GL_TRUE; 225 | if (glewInit() != GLEW_OK) { 226 | std::cerr << "GLEW init failed\n"; 227 | getchar(); 228 | glfwTerminate(); 229 | return; 230 | } 231 | 232 | glEnable(GL_DEPTH_TEST); 233 | glEnable(GL_PROGRAM_POINT_SIZE); 234 | glDepthFunc(GL_LESS); 235 | if (cull_face) glEnable(GL_CULL_FACE); 236 | 237 | #ifdef MESHVIEW_IMGUI 238 | IMGUI_CHECKVERSION(); 239 | ImGui::CreateContext(); 240 | 241 | ImGui_ImplGlfw_InitForOpenGL(window, false); 242 | char* glsl_version = NULL; 243 | ImGui_ImplOpenGL3_Init(glsl_version); 244 | // Setup Dear ImGui style 245 | ImGui::StyleColorsDark(); 246 | glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); 247 | #endif 248 | 249 | // Compile shaders on-the-fly 250 | internal::Shader shader_mesh(MESH_VERTEX_SHADER, MESH_FRAGMENT_SHADER); 251 | internal::Shader shader_mesh_vert_color(MESH_VERTEX_SHADER_VERT_COLOR, 252 | MESH_FRAGMENT_SHADER_VERT_COLOR); 253 | internal::Shader shader_pc(POINTCLOUD_VERTEX_SHADER, 254 | POINTCLOUD_FRAGMENT_SHADER); 255 | 256 | // Construct axes object 257 | PointCloud axes(Eigen::template Map{axes_verts, 6, 3}, 258 | Eigen::template Map{axes_rgb, 6, 3}); 259 | axes.draw_lines(); 260 | axes.update(true); 261 | 262 | // Events 263 | glfwSetKeyCallback(window, win_key_callback); 264 | glfwSetMouseButtonCallback(window, win_mouse_button_callback); 265 | glfwSetCursorPosCallback(window, win_mouse_move_callback); 266 | glfwSetScrollCallback(window, win_scroll_callback); 267 | glfwSetFramebufferSizeCallback(window, win_framebuffer_size_callback); 268 | glfwSetWindowUserPointer(window, this); 269 | 270 | glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL); 271 | 272 | glfwSetWindowTitle(window, title.c_str()); 273 | 274 | if (on_open) on_open(); 275 | 276 | // Ask to re-create the buffers + textures in meshes/pointclouds 277 | for (auto& mesh : meshes) mesh->update(true); 278 | for (auto& pc : point_clouds) pc->update(true); 279 | 280 | auto set_light_and_camera = [&](const internal::Shader& shader) { 281 | shader.set_vec3("light.ambient", light_color_ambient); 282 | shader.set_vec3("light.diffuse", light_color_diffuse); 283 | shader.set_vec3("light.specular", light_color_specular); 284 | shader.set_vec3( 285 | "light.position", 286 | (camera.view.inverse() * light_pos.homogeneous()).head<3>()); 287 | shader.set_vec3("viewPos", camera.get_pos()); 288 | }; 289 | 290 | _looping = true; 291 | while (!glfwWindowShouldClose(window)) { 292 | glClearColor(background[0], background[1], background[2], 1.0f); 293 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 294 | glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL); 295 | if (cull_face) 296 | glEnable(GL_CULL_FACE); 297 | else 298 | glDisable(GL_CULL_FACE); 299 | axes.enable(draw_axes); 300 | 301 | shader_pc.use(); 302 | axes.draw(shader_pc.id, camera); 303 | for (auto& pc : point_clouds) { 304 | pc->draw(shader_pc.id, camera); 305 | } 306 | 307 | shader_mesh.use(); 308 | set_light_and_camera(shader_mesh); 309 | for (auto& mesh : meshes) { 310 | if (mesh->shading_type == Mesh::ShadingType::texture) { 311 | mesh->draw(shader_mesh.id, camera); 312 | } 313 | } 314 | 315 | shader_mesh_vert_color.use(); 316 | set_light_and_camera(shader_mesh_vert_color); 317 | for (auto& mesh : meshes) { 318 | if (mesh->shading_type == Mesh::ShadingType::vertex) { 319 | mesh->draw(shader_mesh_vert_color.id, camera); 320 | } 321 | } 322 | 323 | if (on_loop && on_loop()) { 324 | for (auto& mesh : meshes) mesh->update(true); 325 | for (auto& pc : point_clouds) pc->update(true); 326 | camera.update_proj(); 327 | camera.update_view(); 328 | } 329 | 330 | #ifdef MESHVIEW_IMGUI 331 | glOrtho(0, _width, _height, 0, 0, 1); 332 | 333 | // feed inputs to dear imgui, start new frame 334 | ImGui_ImplOpenGL3_NewFrame(); 335 | ImGui_ImplGlfw_NewFrame(); 336 | ImGui::NewFrame(); 337 | 338 | if (on_gui && on_gui()) { 339 | for (auto& mesh : meshes) mesh->update(true); 340 | for (auto& pc : point_clouds) pc->update(true); 341 | camera.update_proj(); 342 | camera.update_view(); 343 | } 344 | 345 | // Render dear imgui into screen 346 | ImGui::Render(); 347 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 348 | #endif 349 | 350 | glfwSwapBuffers(window); 351 | if (loop_wait_events) { 352 | glfwWaitEvents(); 353 | } else { 354 | glfwPollEvents(); 355 | } 356 | } 357 | _looping = false; 358 | 359 | if (on_close) on_close(); 360 | 361 | for (auto& mesh : meshes) { 362 | mesh->free_bufs(); // Delete any existing buffers to prevent memory 363 | // leak 364 | } 365 | 366 | #ifdef MESHVIEW_IMGUI 367 | ImGui_ImplOpenGL3_Shutdown(); 368 | ImGui_ImplGlfw_Shutdown(); 369 | ImGui::DestroyContext(); 370 | #endif 371 | _window = nullptr; 372 | glfwDestroyWindow(window); 373 | } 374 | 375 | Mesh& Viewer::add_cube(const Eigen::Ref& cen, float side_len, 376 | const Eigen::Ref& color) { 377 | Mesh cube = Mesh::Cube(); 378 | cube.verts_pos() *= side_len; 379 | cube.verts_pos().rowwise() += cen.transpose(); 380 | return add_mesh(std::move(cube)) 381 | .add_texture(color[0], color[1], color[2]) 382 | .template add_texture(color[0], color[1], 383 | color[2]); 384 | } 385 | 386 | Mesh& Viewer::add_square(const Eigen::Ref& cen, float side_len, 387 | const Eigen::Ref& color) { 388 | Mesh sqr = Mesh::Square(); 389 | sqr.verts_pos() *= side_len; 390 | sqr.verts_pos().rowwise() += cen.transpose(); 391 | return add_mesh(std::move(sqr)) 392 | .add_texture(color[0], color[1], color[2]) 393 | .template add_texture(color[0], color[1], 394 | color[2]); 395 | } 396 | 397 | Mesh& Viewer::add_sphere(const Eigen::Ref& cen, float radius, 398 | const Eigen::Ref& color, int rings, 399 | int sectors) { 400 | Mesh sph = Mesh::Sphere(rings, sectors); 401 | sph.verts_pos() *= radius; 402 | sph.verts_pos().rowwise() += cen.transpose(); 403 | return add_mesh(std::move(sph)) 404 | .set_shininess(32.f) 405 | .add_texture(color[0], color[1], color[2]) 406 | .template add_texture(color[0], color[1], 407 | color[2]); 408 | } 409 | 410 | PointCloud& Viewer::add_line(const Eigen::Ref& a, 411 | const Eigen::Ref& b, 412 | const Eigen::Ref& color) { 413 | return add_point_cloud(PointCloud::Line(a, b, color)); 414 | } 415 | 416 | } // namespace meshview 417 | --------------------------------------------------------------------------------