diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b62a62f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "ext/OpenMesh"] + path = ext/OpenMesh + url = https://www.graphics.rwth-aachen.de:9000/OpenMesh/OpenMesh.git +[submodule "ext/nanogui"] + path = ext/nanogui + url = https://github.com/NSchertler/nanogui diff --git a/CMake/Modules/CG1Helper.cmake b/CMake/Modules/CG1Helper.cmake new file mode 100644 index 0000000..4083913 --- /dev/null +++ b/CMake/Modules/CG1Helper.cmake @@ -0,0 +1,83 @@ +FUNCTION(PREPEND var prefix) + SET(listVar "") + FOREACH(f ${ARGN}) + LIST(APPEND listVar "${prefix}/${f}") + ENDFOREACH(f) + SET(${var} "${listVar}" PARENT_SCOPE) +ENDFUNCTION(PREPEND) + +FUNCTION(JOIN VALUES GLUE OUTPUT) + string (REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}") + string (REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping + set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) +ENDFUNCTION() + +FUNCTION(ProcessGLSLFiles GLSL_FILES_VAR) + set(GLSL_FILES ${${GLSL_FILES_VAR}}) + PREPEND(GLSL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/glsl/" ${GLSL_FILES}) + set(${GLSL_FILES_VAR} ${GLSL_FILES} PARENT_SCOPE) + + source_group(glsl FILES ${GLSL_FILES} glsl.cpp glsl.h) + + JOIN("${GLSL_FILES}" "," glsl_string) + set(bin2c_cmdline + -DOUTPUT_C=glsl.cpp + -DOUTPUT_H=glsl.h + "-DINPUT_FILES=${glsl_string}" + -P "${NANOGUI_DIR}/resources/bin2c.cmake") + + add_custom_command( + OUTPUT glsl.cpp glsl.h + COMMAND ${CMAKE_COMMAND} ARGS ${bin2c_cmdline} + DEPENDS ${GLSL_FILES} + COMMENT "Running bin2c" + PRE_BUILD VERBATIM) +ENDFUNCTION() + +FUNCTION(SetupBuildEnvironment) + # Enable folders for projects in Visual Studio + if (CMAKE_GENERATOR MATCHES "Visual Studio") + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + endif() + + # Sanitize build environment for static build with C++11 + if (MSVC) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions (/D "__TBB_NO_IMPLICIT_LINKAGE") + + add_definitions (-DNOMINMAX ) + add_definitions(/D_USE_MATH_DEFINES) + + # Parallel build on MSVC (all targets) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + + if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:SSE2") + + # Disable Eigen vectorization for Windows 32 bit builds (issues with unaligned access segfaults) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DEIGEN_DONT_ALIGN") + endif() + + # Static build + set(CompilerFlags + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + endforeach() + elseif(APPLE) + # Try to auto-detect a suitable SDK + execute_process(COMMAND bash -c "xcodebuild -version -sdk | grep MacOSX | grep Path | head -n 1 | cut -f 2 -d ' '" OUTPUT_VARIABLE CMAKE_OSX_SYSROOT) + string(REGEX REPLACE "(\r?\n)+$" "" CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}") + string(REGEX REPLACE "^.*X([0-9.]*).sdk$" "\\1" CMAKE_OSX_DEPLOYMENT_TARGET "${CMAKE_OSX_SYSROOT}") + endif() + + if(CMAKE_COMPILER_IS_GNUCXX OR CLANG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations -Wno-unused-result") + endif() + + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE) + set (CMAKE_CXX_STANDARD 14 PARENT_SCOPE) +ENDFUNCTION() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d4c9b56 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required (VERSION 3.1) +project(CG1) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMake/Modules/") + +include(CG1Helper) + +set(LIBS) + +SetupBuildEnvironment() + +# Add NanoGUI +set(NANOGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/nanogui) +set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE) +set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE) +set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) +set(NANOGUI_USE_GLAD ON CACHE BOOL " " FORCE) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/nanogui ext_build/nanogui) +set_property(TARGET nanogui nanogui-obj glfw glfw_objects PROPERTY FOLDER "dependencies") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/ext/nanogui/include + ${NANOGUI_EXTRA_INCS}) +set(LIBS ${LIBS} nanogui ${NANOGUI_EXTRA_LIBS}) +add_definitions(${NANOGUI_EXTRA_DEFS}) + +# Add OpenMesh +set(BUILD_APPS OFF CACHE BOOL " " FORCE) +set(OPENMESH_BUILD_PYTHON_BINDINGS OFF CACHE BOOL " " FORCE) +set(OPENMESH_BUILD_SHARED OFF CACHE BOOL " ") +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/OpenMesh ext_build/OpenMesh) +if(TARGET OpenMeshCoreStatic) + set(LIBS ${LIBS} OpenMeshCoreStatic) + set_property(TARGET OpenMeshCoreStatic OpenMeshToolsStatic PROPERTY FOLDER "dependencies") + set_property(TARGET OpenMeshCore OpenMeshToolsStatic PROPERTY EXCLUDE_FROM_ALL TRUE) +else() + set(LIBS ${LIBS} OpenMeshCore) +endif() +include_directories(ext/OpenMesh/src) +set_property(TARGET OpenMeshCore OpenMeshTools PROPERTY FOLDER "dependencies") +set_property(TARGET OpenMeshTools PROPERTY EXCLUDE_FROM_ALL TRUE) + +add_subdirectory(common) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common/include) + +add_subdirectory(exercise1) +add_subdirectory(exercise2) +add_subdirectory(exercise3) +add_subdirectory(exercise4) \ No newline at end of file diff --git a/README.md b/README.md index 5068882..73601a9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ -# cg1 +# Computer Graphics 1 - Exercises +This repository contains the source code and data sets for the CG1 exercises. To download and compile the project, you need to do the following steps: + +1. Clone the project to your local computer +2. Run CMake to generate a project file for your build platform. Alternatively, you can use a development environment that supports CMake natively and open the directory directly. +3. Open the project file and compile. + +Under Unix, you could employ the command line like follows: + + git clone https://bitbucket.org/cgvtud/cg1.git --recursive + cd cg1 + mkdir build + cd build + cmake .. + make + +After this approach, you find the compiled applications under `cg1/build/bin`. + +On Windows / Visual Studio, you can use the CMake GUI. After cloning the repository, open the CMake GUI and set *Where is the source code* to the cloned directory. Set *Where to build the binaries* to a new subfolder `build`. Click *Configure* and select the desired Visual Studio Version. Then, click *Configure* and *Open Project* to generate and open the project in Visual studio. Finally, choose the desired startup project (Exercise1-4) and compile and run. \ No newline at end of file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..3d573bc --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}) + +set(GLSL_FILES mesh.vert mesh.frag + simple.vert simple.frag) +ProcessGLSLFiles(GLSL_FILES) + +add_library(CG1Common STATIC + src/gui/AbstractViewer.cpp + src/gui/Camera.cpp + src/gui/GLBuffer.cpp + src/gui/GLShader.cpp + src/gui/GLVertexArray.cpp + src/gui/SliderHelper.cpp + src/gui/ShaderPool.cpp + + src/util/GLDebug.cpp + src/util/UnionFind.cpp + src/util/OpenMeshUtils.cpp + + glsl.cpp) + \ No newline at end of file diff --git a/common/glsl/mesh.frag b/common/glsl/mesh.frag new file mode 100644 index 0000000..e8f5848 --- /dev/null +++ b/common/glsl/mesh.frag @@ -0,0 +1,40 @@ +#version 130 + +in vec4 normalViewSpace; +in vec4 posViewSpace; +in vec4 vertexColor; +in vec2 vertexTexCoords; + +out vec4 outColor; + +uniform vec4 color; + +uniform bool flatShading; +uniform bool perVertexColor; +uniform bool visualizeTexCoords; + +void main() +{ + vec3 normal = normalize(normalViewSpace.xyz); + if(flatShading) + normal = normalize(cross(dFdx(posViewSpace.xyz), dFdy(posViewSpace.xyz))); + vec3 toLight = normalize(-posViewSpace.xyz); + + float diffuse = abs(dot(normal, toLight)); + float specular = pow(diffuse, 20); + + if(!gl_FrontFacing) + diffuse *= 0.5; + + vec4 materialColor = perVertexColor ? vertexColor : color; + + if(visualizeTexCoords) + { + int cellId = (int(20 * vertexTexCoords.x) + int(20 * vertexTexCoords.y)); + if(cellId % 2 != 0) + materialColor.xyz *= 0.3; + } + + outColor = diffuse * materialColor + specular * vec4(0.5, 0.5, 0.5, 0); + outColor.a = 1; +} \ No newline at end of file diff --git a/common/glsl/mesh.vert b/common/glsl/mesh.vert new file mode 100644 index 0000000..9c58518 --- /dev/null +++ b/common/glsl/mesh.vert @@ -0,0 +1,25 @@ +#version 130 + +in vec4 position; +in vec4 normal; +in vec4 color; +in vec2 texCoords; + +out vec4 normalViewSpace; +out vec4 posViewSpace; +out vec4 vertexColor; +out vec2 vertexTexCoords; + +uniform mat4 view; +uniform mat4 proj; + +void main() +{ + posViewSpace = view * position; + normalViewSpace = view * normal; + + vertexColor = color; + vertexTexCoords = texCoords; + + gl_Position = proj * posViewSpace; +} \ No newline at end of file diff --git a/common/glsl/simple.frag b/common/glsl/simple.frag new file mode 100644 index 0000000..9e7e5a6 --- /dev/null +++ b/common/glsl/simple.frag @@ -0,0 +1,10 @@ +#version 130 + +out vec4 out_color; + +uniform vec4 color; + +void main() +{ + out_color = color; +} \ No newline at end of file diff --git a/common/glsl/simple.vert b/common/glsl/simple.vert new file mode 100644 index 0000000..3167f56 --- /dev/null +++ b/common/glsl/simple.vert @@ -0,0 +1,10 @@ +#version 130 + +in vec4 position; + +uniform mat4 mvp; + +void main() +{ + gl_Position = mvp * position; +} \ No newline at end of file diff --git a/common/include/gui/AbstractViewer.h b/common/include/gui/AbstractViewer.h new file mode 100644 index 0000000..350a0db --- /dev/null +++ b/common/include/gui/AbstractViewer.h @@ -0,0 +1,66 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Nico Schertler +*/ + +#pragma once + +#include + +#include "gui/Camera.h" + +#include + +namespace nse { + namespace gui + { + //This base class provides basic functionality for 3D interaction. + class AbstractViewer : public nanogui::Screen + { + public: + AbstractViewer(const std::string& title, int width = 1280, int height = 800, int nSamples = 4); + + bool scrollEvent(const Eigen::Vector2i & p, const Eigen::Vector2f & rel); + bool mouseButtonEvent(const Eigen::Vector2i & p, int button, bool down, int modifiers); + bool mouseMotionEvent(const Eigen::Vector2i & p, const Eigen::Vector2i & rel, int button, int modifiers); + + virtual bool keyboardEvent(int key, int scancode, int action, int mods); + + virtual bool resizeEvent(const Eigen::Vector2i&); + + const Camera& camera() const { return _camera; } + Camera& camera() { return _camera; } + + bool ctrlDown() const { return _ctrlDown; } + bool shiftDown() const { return _shiftDown; } + + //returns depth buffer value + float get3DPosition(const Eigen::Vector2i& screenPos, Eigen::Vector4f& pos); + float get3DPosition(const Eigen::Vector2i& screenPos, Eigen::Vector3f& pos); + + protected: + nanogui::Window* SetupMainWindow(); + void CheckOpenGLSupport(); + + const int nSamples; + + Camera _camera; + + bool _ctrlDown; + bool _shiftDown; + + std::chrono::high_resolution_clock::time_point lastClickTime; + int lastClickButton; + Eigen::Vector2i lastClickPosition; + + virtual bool scrollHook(const Eigen::Vector2i & p, const Eigen::Vector2f & rel) { return false; } + virtual bool mouseButtonHook(const Eigen::Vector2i & p, int button, bool down, int modifiers) { return false; } + virtual bool mouseMotionHook(const Eigen::Vector2i & p, const Eigen::Vector2i & rel, int button, int modifiers) { return false; } + }; + + } +} \ No newline at end of file diff --git a/common/include/gui/Camera.h b/common/include/gui/Camera.h new file mode 100644 index 0000000..20dd595 --- /dev/null +++ b/common/include/gui/Camera.h @@ -0,0 +1,104 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ + +#pragma once + +#include +#include + +#include "math/BoundingBox.h" + +namespace nse { + namespace gui + { + + //Provides a 3D camera with arcball interaction capabilities. + class Camera + { + public: + + //Initialize the camera. parent is the widget onto which the scene is rendered and is only used to query its size. + Camera(const nanogui::Widget& parent); + + //Computes view and projection matrix for the current camera state. + void ComputeCameraMatrices( + Eigen::Matrix4f &view, + Eigen::Matrix4f &proj, + float customAspectRatio = 0) const; + + //Applies a zoom by scaling the scene. Positive values of amount increase object sizes. + void Zoom(float amount); + + //Sets the extent of the scene, which is kept between znear/zfar. + void SetSceneExtent(const nse::math::BoundingBox& bbox); + + //Translates and zooms the camera in a way that it shows the entire bounding box while keeping the orientation. + //The parameters are only estimated; it is not guaranteed that the bounding box actually fits in the viewport. + void FocusOnBBox(const nse::math::BoundingBox& bbox); + + //Keeps the camera's rotation and viewing distance and sets the focus point to the provided location. + void FocusOnPoint(const Eigen::Vector3f& point); + + //Rotates the camera, such that it becomes horizontally aligned + void MakeHorizontal(); + + //Rotates the camera around the current focus point by rotation. The rotation is expressed in the + //camera's local coordinate system. + void RotateAroundFocusPointLocal(const Eigen::Quaternionf& rotation); + + //Rotates the camera around the current focus point by rotation. The rotation is expressed in the + //global coordinate system. + void RotateAroundFocusPointGlobal(const Eigen::Quaternionf& rotation); + + //Forwarded mouse button event. + bool HandleMouseButton(const Eigen::Vector2i &p, int button, bool down, int modifiers); + //Forwarded mouse move event. + bool HandleMouseMove(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, int button, int modifiers); + //Forwarded resize event. + void resize(const Eigen::Vector2i & s); + + //Returns the point that the camera focuses on + const Eigen::Vector3f& GetFocusPoint() const; + + struct CamParams + { + nanogui::Arcball arcball; + + //bounding sphere of scene + Eigen::Vector3f sceneCenter; + float sceneRadius; + + float fovy = 45.0f; + Eigen::Vector3f focusPoint = Eigen::Vector3f::Zero(); + float viewDistance = 5; + }; + + //Returns the current camera parameters + const CamParams& saveParams() const { return params; } + //Restores the camera parameters that have been previously saved. + void restoreParams(const CamParams& params) { this->params = params; } + + private: + const nanogui::Widget& parent; + + enum InteractionMode + { + None, + Translate, + Rotate + } interactionMode = None; + + CamParams params; + + Eigen::Vector3f modelTranslation_start = Eigen::Vector3f::Zero(); + Eigen::Vector2i translation_start; //mouse position on the screen where translation started + }; + } +} \ No newline at end of file diff --git a/common/include/gui/GLBuffer.h b/common/include/gui/GLBuffer.h new file mode 100644 index 0000000..ffabb20 --- /dev/null +++ b/common/include/gui/GLBuffer.h @@ -0,0 +1,105 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ + +#pragma once + +#include + +namespace nse { + namespace gui + { + + enum GLBufferType + { + VertexBuffer, + IndexBuffer, + }; + + //Represents a generic OpenGL buffer + class GLBuffer + { + public: + GLBuffer(GLBufferType type); + ~GLBuffer(); + + //Binds the buffer to the correct target slot. + //If the buffer is not generated yet, a new buffer is generated. + void bind(); + //Calls the according OpenGL function; mostly used for SSBOs + void bindBufferBase(unsigned int base) const; + + /// Upload data from an Eigen matrix into the buffer + template + GLBuffer& uploadData(const Matrix &M) + { + uint32_t compSize = sizeof(typename Matrix::Scalar); + GLuint glType = (GLuint)nanogui::detail::type_traits::type; + bool integral = (bool)nanogui::detail::type_traits::integral; + + uploadData((uint32_t)M.size(), (int)M.rows(), compSize, + glType, integral, (const uint8_t *)M.data()); + + return *this; + } + + template + GLBuffer& uploadData(const std::vector>& data) + { + uint32_t compSize = sizeof(Scalar); + GLuint glType = (GLuint)nanogui::detail::type_traits::type; + bool integral = (bool)nanogui::detail::type_traits::integral; + + uploadData((uint32_t)data.size() * Rows, Rows, compSize, + glType, integral, (const uint8_t *)data.data()); + + return *this; + } + + /// Download the data from the vertex buffer object into an Eigen matrix + template + void downloadData(Matrix &M) + { + uint32_t compSize = sizeof(typename Matrix::Scalar); + GLuint glType = (GLuint)nanogui::detail::type_traits::type; + + M.resize(dim, size / dim); + + downloadData(M.size(), M.rows(), compSize, glType, (uint8_t *)M.data()); + } + + //Binds the vertex buffer to the provided attribute of the currently bound GLShader program. + void bindToAttribute(const std::string& attribute); + + /// Return the size of this buffer in bytes + size_t bufferSize() const + { + return size; + } + + void uploadData(uint32_t size, const void *data); + void uploadData(uint32_t size, int dim, + uint32_t compSize, GLuint glType, bool integral, + const uint8_t *data); + void downloadData(uint32_t size, int dim, + uint32_t compSize, GLuint glType, uint8_t *data); + + + protected: + GLBufferType type; + + GLuint id; + GLuint glType; + GLuint dim; + GLuint compSize; + GLuint size; + bool integral; + }; + } +} \ No newline at end of file diff --git a/common/include/gui/GLShader.h b/common/include/gui/GLShader.h new file mode 100644 index 0000000..38f2e37 --- /dev/null +++ b/common/include/gui/GLShader.h @@ -0,0 +1,138 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ + +#pragma once +//Adapted nanogui shader + +#include +#include + +namespace nse { + namespace gui + { + + //Represents an OpenGL shader program + class GLShader + { + public: + /// Create an unitialized OpenGL shader program + GLShader() + : mVertexShader(0), mFragmentShader(0), mGeometryShader(0), + mProgramShader(0) { } + + ~GLShader(); + + /// Initialize the shader using the specified source strings + bool init(const std::string &name, const std::string &vertex_str, + const std::string &fragment_str, + const std::string &geometry_str = "", + bool failSilently = false); + +#ifdef HAVE_TESSELLATION + bool initWithTessellation(const std::string &name, const std::string &vertex_str, + const std::string &tessellation_control_str, + const std::string &tessellation_eval_str, + const std::string &fragment_str, + const std::string &geometry_str = "", + bool failSilently = false); +#endif + + /// Initialize the shader using the specified files on disk + bool initFromFiles(const std::string &name, + const std::string &vertex_fname, + const std::string &fragment_fname, + const std::string &geometry_fname = ""); + + /// Return the name of the shader + const std::string &name() const { return mName; } + + /// Set a preprocessor definition + void define(const std::string &key, const std::string &value) { mDefinitions[key] = value; } + + /// Select this shader for subsequent draw calls + void bind(); + + /// Return the handle of a named shader attribute (-1 if it does not exist) + GLint attrib(const std::string &name, bool warn = true) const; + + /// Return the handle of a uniform attribute (-1 if it does not exist) + GLint uniform(const std::string &name, bool warn = true) const; + + /// Initialize a uniform parameter with a 4x4 matrix (float) + template + void setUniform(const std::string &name, const Eigen::Matrix &mat, bool warn = true) { + glUniformMatrix4fv(uniform(name, warn), 1, GL_FALSE, mat.template cast().data()); + } + + /// Initialize a uniform parameter with an integer value + template ::integral == 1, int>::type = 0> + void setUniform(const std::string &name, T value, bool warn = true) { + glUniform1i(uniform(name, warn), (int)value); + } + + /// Initialize a uniform parameter with a floating point value + template ::integral == 0, int>::type = 0> + void setUniform(const std::string &name, T value, bool warn = true) { + glUniform1f(uniform(name, warn), (float)value); + } + + /// Initialize a uniform parameter with a 2D vector (int) + template ::integral == 1, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform2i(uniform(name, warn), (int)v.x(), (int)v.y()); + } + + /// Initialize a uniform parameter with a 2D vector (float) + template ::integral == 0, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform2f(uniform(name, warn), (float)v.x(), (float)v.y()); + } + + /// Initialize a uniform parameter with a 3D vector (int) + template ::integral == 1, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform3i(uniform(name, warn), (int)v.x(), (int)v.y(), (int)v.z()); + } + + /// Initialize a uniform parameter with a 3D vector (float) + template ::integral == 0, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform3f(uniform(name, warn), (float)v.x(), (float)v.y(), (float)v.z()); + } + + /// Initialize a uniform parameter with a 4D vector (int) + template ::integral == 1, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform4i(uniform(name, warn), (int)v.x(), (int)v.y(), (int)v.z(), (int)v.w()); + } + + /// Initialize a uniform parameter with a 4D vector (float) + template ::integral == 0, int>::type = 0> + void setUniform(const std::string &name, const Eigen::Matrix &v, bool warn = true) { + glUniform4f(uniform(name, warn), (float)v.x(), (float)v.y(), (float)v.z(), (float)v.w()); + } + + //Returns the shader program that has been bound last. + static GLShader* currentProgram(); + + protected: + static GLShader* _currentProgram; + + std::string mName; + GLuint mVertexShader; + GLuint mTessellationControlShader; + GLuint mTessellationEvalShader; + GLuint mFragmentShader; + GLuint mGeometryShader; + GLuint mProgramShader; + std::map mDefinitions; + }; + } +} \ No newline at end of file diff --git a/common/include/gui/GLVertexArray.h b/common/include/gui/GLVertexArray.h new file mode 100644 index 0000000..86f2f7c --- /dev/null +++ b/common/include/gui/GLVertexArray.h @@ -0,0 +1,40 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Nico Schertler +*/ + +#pragma once + +#include + +namespace nse { + namespace gui + { + //Represents an OpenGL Vertex Array Object + class GLVertexArray + { + public: + GLVertexArray(); + ~GLVertexArray(); + + //Generates the VAO if it has not been generated yet. + void generate(); + + //Binds the VAO. + void bind() const; + + //Binds a zero VAO. + void unbind() const; + + //Returns if this VAO has been generated. + bool valid() const; + + private: + GLuint vaoId; + }; + } +} \ No newline at end of file diff --git a/common/include/gui/ShaderPool.h b/common/include/gui/ShaderPool.h new file mode 100644 index 0000000..faee791 --- /dev/null +++ b/common/include/gui/ShaderPool.h @@ -0,0 +1,20 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include + +struct ShaderPool +{ +private: + static ShaderPool* _instance; +public: + static ShaderPool* Instance(); + + void CompileShaders(); + + nse::gui::GLShader meshShader; + nse::gui::GLShader simpleShader; +}; \ No newline at end of file diff --git a/common/include/gui/SliderHelper.h b/common/include/gui/SliderHelper.h new file mode 100644 index 0000000..b0190dd --- /dev/null +++ b/common/include/gui/SliderHelper.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace nse +{ + namespace gui + { + nanogui::Slider* AddWidgetWithSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::Widget*& outWidget); + + nanogui::Slider* AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue); + + nanogui::Slider* AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::TextBox*& out_label); + + nanogui::Slider* AddLabeledSliderWithDefaultDisplay(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, std::streamsize displayPrecision); + + class VectorInput + { + public: + VectorInput(nanogui::Widget* parent, const std::string& prefix, const Eigen::Vector3f& min, const Eigen::Vector3f& max, const Eigen::Vector3f& current, std::function callback); + + Eigen::Vector3f Value() const; + + void SetBounds(const Eigen::Vector3f& lower, const Eigen::Vector3f& upper); + void SetValue(const Eigen::Vector3f& value); + + private: + nanogui::Slider *sldX, *sldY, *sldZ; + }; + } +} \ No newline at end of file diff --git a/common/include/math/BoundingBox.h b/common/include/math/BoundingBox.h new file mode 100644 index 0000000..d2124bb --- /dev/null +++ b/common/include/math/BoundingBox.h @@ -0,0 +1,122 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Nico Schertler +*/ + +#pragma once + +#include + +namespace nse { + namespace math + { + + //Represents an n-dimensional axis-aligned bounding box. The lower limits are inclusive, + //whereas the upper limits are exclusive. + template + struct BoundingBox + { + Eigen::Matrix min, max; + + //Initializes the bounding box with an empty area. + BoundingBox() + { + reset(); + } + + BoundingBox(const Eigen::Matrix& min, const Eigen::Matrix& max) + : min(min), max(max) + { } + + //Resets the bounding box to an empty area. + void reset() + { + min.setConstant(std::numeric_limits::max()); + max.setConstant(std::numeric_limits::lowest()); + } + + //Computes the intersection of two axis-aligned bounding boxes and returns if there is an intersection. + static bool intersect(const BoundingBox& bb1, const BoundingBox& bb2, BoundingBox& out) + { + for (int i = 0; i < DIM; ++i) + { + out.min[i] = std::max(bb1.min[i], bb2.min[i]); + if (out.min[i] > bb1.max[i] || out.min[i] > bb2.max[i]) + return false; + + out.max[i] = std::min(bb1.max[i], bb2.max[i]); + if (out.max[i] < bb1.min[i] || out.max[i] < bb2.min[i]) + return false; + } + return true; + } + + //Computes the union of two axis-aligned bounding boxes. + static void unite(const BoundingBox& bb1, const BoundingBox& bb2, BoundingBox& out) + { + for (int i = 0; i < DIM; ++i) + { + out.min[i] = std::min(bb1.min[i], bb2.min[i]); + out.max[i] = std::max(bb1.max[i], bb2.max[i]); + } + } + + //expands the bounding box to contain every point in V + void expand(const Eigen::Matrix& V) + { + for (int v = 0; v < V.cols(); ++v) + { + for (int i = 0; i < DIM; ++i) + { + if (V(i, v) < min(i)) + min(i) = V(i, v); + if (V(i, v) >= max(i)) + max(i) = nextafter(V(i, v), std::numeric_limits::max()); + } + } + } + + //Returns if the bounding box contains the given point. + bool containsPoint(const Eigen::Matrix& v) const + { + for (int i = 0; i < DIM; ++i) + { + if (v(i) < min(i) || v(i) >= max(i)) + return false; + } + return true; + } + + //Returns the box's diagonal + Eigen::Matrix diagonal() const { return max - min; } + + //Returns the box's center + Eigen::Matrix center() const { return 0.5f * (min + max); } + + //Transforms the bounding box with a given affine transform and returns the axis-aligned bounding box + //of the transformed box. + BoundingBox transform(const Eigen::Transform& t) const + { + //http://dev.theomader.com/transform-bounding-boxes/ + + Eigen::Matrix tMin = t.translation(); + Eigen::Matrix tMax = t.translation(); + + for (int i = 0; i < DIM; ++i) + { + Eigen::Matrix a = t.linear().col(i) * min(i); + Eigen::Matrix b = t.linear().col(i) * max(i); + + tMin += a.cwiseMin(b); + tMax += a.cwiseMax(b); + } + + return BoundingBox(tMin, tMax); + } + }; + } +} \ No newline at end of file diff --git a/common/include/util/GLDebug.h b/common/include/util/GLDebug.h new file mode 100644 index 0000000..0979a9e --- /dev/null +++ b/common/include/util/GLDebug.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace nse +{ + namespace util + { + class GLDebug + { + public: + static void IgnoreGLError(GLuint errorId); + static void SetupDebugCallback(); + + private: + static std::unordered_set ignoredIds; + }; + } +} \ No newline at end of file diff --git a/common/include/util/OpenMeshUtils.h b/common/include/util/OpenMeshUtils.h new file mode 100644 index 0000000..555ed06 --- /dev/null +++ b/common/include/util/OpenMeshUtils.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include +#include +#include + +typedef OpenMesh::PolyMesh_ArrayKernelT<> HEMesh; + +//Converts an OpenMesh vector to an Eigen vector +static Eigen::Vector3f ToEigenVector(const OpenMesh::Vec3f& v) +{ + return Eigen::Vector3f(v[0], v[1], v[2]); +} + +//Converts an OpenMesh vector to an Eigen vector +static Eigen::Vector2f ToEigenVector(const OpenMesh::Vec2f& v) +{ + return Eigen::Vector2f(v[0], v[1]); +} + +//Converts an Eigen vector to an OpenMesh vector +static OpenMesh::Vec3f ToOpenMeshVector(const Eigen::Vector3f& v) +{ + return OpenMesh::Vec3f(v[0], v[1], v[2]); +} + +//Converts an OpenMesh vector to an Eigen vector +static Eigen::Vector4f ToEigenVector4(const OpenMesh::Vec3f& v, float w = 1) +{ + return Eigen::Vector4f(v[0], v[1], v[2], w); +} + +//Converts an OpenMesh vector to an Eigen vector +static Eigen::Vector4f ToEigenVector4(const OpenMesh::Vec2f& v, float z = 0, float w = 1) +{ + return Eigen::Vector4f(v[0], v[1], z, w); +} + +//GPU representation of a mesh with rendering capabilities. +class MeshRenderer +{ +public: + MeshRenderer(const HEMesh& mesh); + + //Update the underlying buffers based on the current geometry in the referenced mesh + void Update(); + + void UpdateWithPerFaceColor(OpenMesh::FPropHandleT colorProperty); + + void Render(const Eigen::Matrix4f& view, const Eigen::Matrix4f& projection, bool flatShading = false, bool withTexCoords = false, const Eigen::Vector4f& color = Eigen::Vector4f(0.8f, 0.7f, 0.6f, 1.0f)) const; + void RenderTextureMap(const Eigen::Matrix4f& projection, const Eigen::Vector4f& color) const; + +private: + + void UpdateTextureMapBuffers(); + + const HEMesh& mesh; + + nse::gui::GLBuffer positionBuffer, normalBuffer, colorBuffer, texCoordBuffer, texCoordBuffer4D; + nse::gui::GLBuffer indexBuffer, indexBufferTexCoords; + unsigned int indexCount; + unsigned int indexCountTexCoords; + nse::gui::GLVertexArray vao, vaoTexCoords; + + bool hasColor = false; +}; \ No newline at end of file diff --git a/common/include/util/UnionFind.h b/common/include/util/UnionFind.h new file mode 100644 index 0000000..61e9662 --- /dev/null +++ b/common/include/util/UnionFind.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +namespace nse +{ + namespace util + { + // Represents a union-find data structure (disjoint sets). + class UnionFind + { + // Node is root iff parent index == index + + public: + typedef unsigned int index_t; + + // Saves the entire structure to a file for later usage + void SaveToFile(const char* filename) const; + + // Returns the number of entries + std::size_t size() const; + + // Loads the entire structure from a file. Existing data in the structure is overridden. + void LoadFromFile(const char* filename); + + // Adds an item to the structure + void AddItem(); + + void AddItems(std::size_t count); + + void Clear(); + + // Finds the set representative for a given entry. Two entries are in the same set + // iff they have the same set representative. + index_t GetRepresentative(index_t index); + + // Merges the sets of the two specified entries. Returns the new root. + index_t Merge(index_t i1, index_t i2); + + // Merges the sets of the two specified entries, such that the specified entry will be the new root of the subtree. + //newRoot must already be the representative of itself. + void MergeWithPredefinedRoot(index_t newRoot, index_t i); + + private: + void ConcreteMerge(index_t newRoot, index_t child); + + std::vector parentIndices; + std::vector ranks; + }; + } +} \ No newline at end of file diff --git a/common/src/gui/AbstractViewer.cpp b/common/src/gui/AbstractViewer.cpp new file mode 100644 index 0000000..2327fb5 --- /dev/null +++ b/common/src/gui/AbstractViewer.cpp @@ -0,0 +1,207 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Nico Schertler +*/ + +#include "gui/AbstractViewer.h" + +#include +#include +#include +#include + +using namespace nse::gui; + +AbstractViewer::AbstractViewer(const std::string& title, int width, int height, int nSamples) + : nanogui::Screen(Eigen::Vector2i(width, height), title, true, false, 8, 8, 24, 8, nSamples), + _camera(*this), _ctrlDown(false), _shiftDown(false), nSamples(nSamples) +{ +} + +nanogui::Window* AbstractViewer::SetupMainWindow() +{ + auto mainWindow = new nanogui::Window(this, this->caption()); + mainWindow->setPosition(Eigen::Vector2i(15, 15)); + mainWindow->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); + + CheckOpenGLSupport(); + + return mainWindow; +} + +// Check if the correct version of OpenGL is supported +void AbstractViewer::CheckOpenGLSupport() +{ + char tempBuffer[255]; + char* oglVersion = (char*)glGetString(GL_VERSION); + strcpy(tempBuffer, oglVersion); + char* token = strtok(tempBuffer, ". "); + std::vector oglVersionNumbers; + while (token != nullptr) + { + try + { + oglVersionNumbers.push_back(std::atoi(token)); + token = strtok(nullptr, ". "); + } + catch (...) + { + std::cout << "Error parsing OpenGL version string " << oglVersion << std::endl; + return; + } + } + + char* glslVersion = (char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + strcpy(tempBuffer, oglVersion); + token = strtok(tempBuffer, ". "); + std::vector glslVersionNumbers; + while (token != nullptr) + { + try + { + glslVersionNumbers.push_back(std::atoi(token)); + token = strtok(nullptr, ". "); + } + catch (...) + { + std::cout << "Error parsing GLSL version string " << glslVersion << std::endl; + return; + } + } + + if (oglVersionNumbers.size() < 1 || glslVersionNumbers.size() < 2) + { + std::cout << "Error parsing OpenGL or GLSL version numbers." << std::endl; + return; + } + + if (oglVersionNumbers[0] < 3 || (glslVersionNumbers[1] < 3 && glslVersionNumbers[0] == 1)) + { + std::cout + << "ERROR: YOUR VERSION OF OPENGL IS TOO OLD!" << std::endl + << " - Needed Opengl-Version: 3.0. On this system: " << glGetString(GL_VERSION) << std::endl + << " - Needed GLSL-Version: 1.3. On this system: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl; + } +} + + + +bool AbstractViewer::keyboardEvent(int key, int scancode, int action, int mods) +{ + if (key == GLFW_KEY_LEFT_CONTROL && action == 0) + _ctrlDown = false; + if (key == GLFW_KEY_LEFT_CONTROL && action == 1) + _ctrlDown = true; + + if (key == GLFW_KEY_LEFT_SHIFT && action == 0) + _shiftDown = false; + if (key == GLFW_KEY_LEFT_SHIFT && action == 1) + _shiftDown = true; + + return true; +} + +bool AbstractViewer::scrollEvent(const Eigen::Vector2i &p, const Eigen::Vector2f &rel) +{ + if (Screen::scrollEvent(p, rel)) + return true; + + if (scrollHook(p, rel)) + return true; + + if (!_ctrlDown && !_shiftDown) + { + _camera.Zoom(rel.y()); + return true; + } + + return false; +} + +bool AbstractViewer::mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) +{ + if (Screen::mouseButtonEvent(p, button, down, modifiers) && down) + return true; + + if (mouseButtonHook(p, button, down, modifiers) && down) + return true; + + auto now = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(now - lastClickTime).count() < 300 && !down && button == lastClickButton && (p - lastClickPosition).cwiseAbs().sum() <= 2) + { + if (button == GLFW_MOUSE_BUTTON_LEFT) + { + Eigen::Vector3f point; + auto depth = get3DPosition(p, point); + if (depth < 1) + { + _camera.FocusOnPoint(point); + glfwSetCursorPos(mGLFWWindow, width() / 2, height() / 2); + } + } + } + + if (!down) + { + lastClickTime = now; + lastClickButton = button; + lastClickPosition = p; + } + + return _camera.HandleMouseButton(p, button, down, modifiers); +} + +bool AbstractViewer::mouseMotionEvent(const Eigen::Vector2i &p, const Eigen::Vector2i &rel, + int button, int modifiers) +{ + if (Screen::mouseMotionEvent(p, rel, button, modifiers)) + return true; + + if (mouseMotionHook(p, rel, button, modifiers)) + return true; + + return _camera.HandleMouseMove(p, rel, button, modifiers); +} + +bool AbstractViewer::resizeEvent(const Eigen::Vector2i & s) +{ + Screen::resizeEvent(s); + _camera.resize(s); + return true; +} + +float AbstractViewer::get3DPosition(const Eigen::Vector2i & screenPos, Eigen::Vector4f & pos) +{ + float depth; + glReadPixels(screenPos.x(), height() - 1 - screenPos.y(), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); + + float ndcDepth = 2 * (depth - 0.5f); + + float x = 2 * ((float)screenPos.x() / width() - 0.5f); + float y = 2 * ((float)-screenPos.y() / height() + 0.5f); + + Eigen::Matrix4f view, proj; + camera().ComputeCameraMatrices(view, proj); + + Eigen::Matrix4f mvp = proj * view; + Eigen::Matrix4f invMvp = mvp.inverse(); + + pos = invMvp * Eigen::Vector4f(x, y, ndcDepth, 1); + pos /= pos.w(); + + return depth; +} + +float AbstractViewer::get3DPosition(const Eigen::Vector2i & screenPos, Eigen::Vector3f & pos) +{ + Eigen::Vector4f pos4; + float depth = get3DPosition(screenPos, pos4); + pos.x() = pos4.x(); + pos.y() = pos4.y(); + pos.z() = pos4.z(); + return depth; +} \ No newline at end of file diff --git a/common/src/gui/Camera.cpp b/common/src/gui/Camera.cpp new file mode 100644 index 0000000..77eff96 --- /dev/null +++ b/common/src/gui/Camera.cpp @@ -0,0 +1,177 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ + +#include +#include "gui/Camera.h" + +using namespace nse::gui; + +Camera::Camera(const nanogui::Widget & parent) + : parent(parent) +{ + params.arcball.setSize(parent.size()); +} + +void Camera::ComputeCameraMatrices(Eigen::Matrix4f & view, Eigen::Matrix4f & proj, float customAspectRatio) const +{ + auto arcBallMatrix = params.arcball.matrix(); + Eigen::Vector3f viewDirection = -arcBallMatrix.row(2).head<3>(); + Eigen::Vector3f cameraPosition = params.focusPoint - viewDirection * params.viewDistance; + + float depthOfSceneCenter = (params.sceneCenter - cameraPosition).dot(viewDirection); + float minZNear = 0.001f * params.sceneRadius; + float znear = std::max(minZNear, depthOfSceneCenter - params.sceneRadius); + float zfar = std::max(znear + minZNear, depthOfSceneCenter + params.sceneRadius); + + float fH = std::tan(params.fovy / 360.0f * (float)M_PI) * znear; + float aspectRatio = customAspectRatio == 0 ? (float)parent.width() / parent.height() : customAspectRatio; + float fW = fH * aspectRatio; + + proj = nanogui::frustum(-fW, fW, -fH, fH, znear, zfar); + + view = nanogui::translate(Eigen::Vector3f(0, 0, -params.viewDistance)) * params.arcball.matrix() * nanogui::translate(-params.focusPoint); +} + +void Camera::Zoom(float amount) +{ + params.viewDistance = std::max(0.001f * params.sceneRadius, params.viewDistance * std::pow(0.90f, amount)); +} + +void Camera::SetSceneExtent(const nse::math::BoundingBox& bbox) +{ + params.sceneCenter = 0.5f * (bbox.min + bbox.max); + params.sceneRadius = bbox.diagonal().norm() / 2.0f; +} + +void Camera::FocusOnBBox(const nse::math::BoundingBox& bbox) +{ + SetSceneExtent(bbox); + + params.focusPoint = params.sceneCenter; + + float fov = params.fovy * std::min(1.0f, (float)parent.width() / parent.height()); + params.viewDistance = params.sceneRadius / sinf(fov / 2.0f * (float)M_PI / 180); +} + +void Camera::FocusOnPoint(const Eigen::Vector3f& point) +{ + params.focusPoint = point; +} + +const Eigen::Vector3f& Camera::GetFocusPoint() const +{ + return params.focusPoint; +} + +#include +void Camera::MakeHorizontal() +{ + //the camera matrix C (=inverse view matrix) + auto matrix = params.arcball.matrix().transpose(); + Eigen::Vector3f currentXAxis = matrix.col(0).head<3>(); + Eigen::Vector3f currentYAxis = matrix.col(1).head<3>(); + + //solve for a rotation R about the z-axis, such that [C * R * currentXAxis].y = 0 + //find the target of the x-axis in the plane: s * x + t * y, s.t. s^2 + t^2 = 1 + + float xy = currentXAxis.y(); + float yy = currentYAxis.y(); + float s = sqrt(1.0f / (1 + (xy * xy) / (yy * yy))); + float t = -s * xy / yy; + float angle = std::atan2(t, s); + params.arcball.state() = Eigen::AngleAxisf(-angle, Eigen::Vector3f::UnitZ()) * params.arcball.state(); +} + +void Camera::RotateAroundFocusPointGlobal(const Eigen::Quaternionf& rotation) +{ + params.arcball.state() *= rotation.conjugate(); +} + +void Camera::RotateAroundFocusPointLocal(const Eigen::Quaternionf& rotation) +{ + params.arcball.state() = rotation.conjugate() * params.arcball.state(); +} + +bool Camera::HandleMouseButton(const Eigen::Vector2i & p, int button, bool down, int modifiers) +{ + if (button == GLFW_MOUSE_BUTTON_1 && modifiers == 0) + { + if (down) + { + if (interactionMode == None) + { + interactionMode = Rotate; + params.arcball.button(p, down); + return true; + } + } + else + { + if (interactionMode == Rotate) + { + params.arcball.button(p, false); + interactionMode = None; + return true; + } + } + } + else if (button == GLFW_MOUSE_BUTTON_2 || + (button == GLFW_MOUSE_BUTTON_1 && modifiers == GLFW_MOD_SHIFT)) + { + if (down) + { + if (interactionMode == None) + { + modelTranslation_start = params.focusPoint; + translation_start = p; + interactionMode = Translate; + return true; + } + } + else + { + if (interactionMode == Translate) + { + interactionMode = None; + return true; + } + } + } + + return false; +} + +bool Camera::HandleMouseMove(const Eigen::Vector2i & p, const Eigen::Vector2i & rel, int button, int modifiers) +{ + if (interactionMode == Rotate) + { + params.arcball.motion(p); + return true; + } + else if (interactionMode == Translate) + { + Eigen::Matrix4f view, proj; + ComputeCameraMatrices(view, proj); + + Eigen::Vector2f current(2.0f * p.x() / parent.height() - 1.0f, -2.0f * p.y() / parent.height() + 1.0f); + Eigen::Vector2f start(2.0f * translation_start.x() / parent.height() - 1.0f, -2.0f * translation_start.y() / parent.height() + 1.0f); + auto rel = (current - start) * params.viewDistance * tanf(params.fovy / 2 * (float)M_PI / 180); + params.focusPoint = modelTranslation_start - (rel.x() * view.block<1, 3>(0, 0).transpose() + rel.y() * view.block<1, 3>(1, 0).transpose()); + + return true; + } + + return false; +} + +void Camera::resize(const Eigen::Vector2i & s) +{ + params.arcball.setSize(s); +} \ No newline at end of file diff --git a/common/src/gui/GLBuffer.cpp b/common/src/gui/GLBuffer.cpp new file mode 100644 index 0000000..501ed52 --- /dev/null +++ b/common/src/gui/GLBuffer.cpp @@ -0,0 +1,113 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ +#include "gui/GLBuffer.h" +#include "gui/GLShader.h" +#include + +using namespace nse::gui; + +const GLuint BufferTargets[] = +{ + GL_ARRAY_BUFFER, + GL_ELEMENT_ARRAY_BUFFER +#ifdef HAVE_SSBO + , GL_SHADER_STORAGE_BUFFER +#endif +}; + +GLBuffer::GLBuffer(GLBufferType type) + : id(0), type(type) +{} + +GLBuffer::~GLBuffer() +{ + if (id != 0) + glDeleteBuffers(1, &id); +} + +void GLBuffer::bind() +{ + if (id == 0) + glGenBuffers(1, &id); + + glBindBuffer(BufferTargets[type], id); +} + +void GLBuffer::bindBufferBase(unsigned int base) const +{ + glBindBufferBase(BufferTargets[type], base, id); + +} + +void GLBuffer::bindToAttribute(const std::string& attribute) +{ + assert(type == VertexBuffer); + + GLShader* prog = GLShader::currentProgram(); + auto attribID = prog->attrib(attribute); + if (attribID >= 0) + { + glEnableVertexAttribArray(attribID); + bind(); + if (integral) + glVertexAttribIPointer(attribID, dim, glType, 0, 0); + else + glVertexAttribPointer(attribID, dim, glType, GL_FALSE, 0, 0); + } + else + std::cout << "Warning: Attribute \"" << attribute << "\" not found in shader \"" << prog->name() << "\"." << std::endl; +} + +void GLBuffer::uploadData(uint32_t size, int dim, + uint32_t compSize, GLuint glType, bool integral, + const uint8_t *data) +{ + this->glType = glType; + this->dim = dim; + this->compSize = compSize; + this->size = size; + this->integral = integral; + + size_t totalSize = (size_t)size * (size_t)compSize; + + bind(); + + glBufferData(BufferTargets[type], totalSize, data, GL_DYNAMIC_DRAW); +} + + +void GLBuffer::uploadData(uint32_t size, const void *data) +{ + this->glType = -1; + this->dim = -1; + this->compSize = -1; + this->size = size; + this->integral = false; + + bind(); + + glBufferData(BufferTargets[type], size, data, GL_DYNAMIC_DRAW); +} + +void GLBuffer::downloadData(uint32_t size, int /* dim */, + uint32_t compSize, GLuint /* glType */, uint8_t *data) +{ + if(id == 0) + throw std::runtime_error("The specified buffer has no data to download."); + + if (this->size != size || this->compSize != compSize) + throw std::runtime_error("downloadData: size mismatch!"); + + size_t totalSize = (size_t)size * (size_t)compSize; + + bind(); + + glGetBufferSubData(BufferTargets[type], 0, totalSize, data); +} \ No newline at end of file diff --git a/common/src/gui/GLShader.cpp b/common/src/gui/GLShader.cpp new file mode 100644 index 0000000..1810e63 --- /dev/null +++ b/common/src/gui/GLShader.cpp @@ -0,0 +1,254 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Wenzel Jakob + @author Nico Schertler +*/ + +#include "gui/GLShader.h" + +#include +#include + +using namespace nse::gui; + +GLShader* GLShader::_currentProgram = nullptr; + +GLShader* GLShader::currentProgram() +{ + return _currentProgram; +} + +GLShader::~GLShader() +{ + glDeleteProgram(mProgramShader); mProgramShader = 0; + glDeleteShader(mVertexShader); mVertexShader = 0; + glDeleteShader(mTessellationControlShader); mTessellationControlShader = 0; + glDeleteShader(mTessellationEvalShader); mTessellationEvalShader = 0; + glDeleteShader(mFragmentShader); mFragmentShader = 0; + glDeleteShader(mGeometryShader); mGeometryShader = 0; +} + +static GLuint createShader_helper(GLint type, const std::string &name, + const std::string &defines, + std::string shader_string, + bool failSilently) +{ + if (shader_string.empty()) + return (GLuint)0; + + if (!defines.empty()) + { + if (shader_string.length() > 8 && shader_string.substr(0, 8) == "#version") + { + std::istringstream iss(shader_string); + std::ostringstream oss; + std::string line; + std::getline(iss, line); + oss << line << std::endl; + oss << defines; + while (std::getline(iss, line)) + oss << line << std::endl; + shader_string = oss.str(); + } + else + { + shader_string = defines + shader_string; + } + } + + GLuint id = glCreateShader(type); + const char *shader_string_const = shader_string.c_str(); + glShaderSource(id, 1, &shader_string_const, nullptr); + glCompileShader(id); + + GLint status; + glGetShaderiv(id, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + if (failSilently) + return 0; + + char buffer[512]; + std::cerr << "Error while compiling "; + if (type == GL_VERTEX_SHADER) + std::cerr << "vertex shader"; + else if (type == GL_FRAGMENT_SHADER) + std::cerr << "fragment shader"; + else if (type == GL_GEOMETRY_SHADER) + std::cerr << "geometry shader"; +#ifdef HAVE_TESSELLATION + else if (type == GL_TESS_CONTROL_SHADER) + std::cerr << "tessellation control shader"; + else if (type == GL_TESS_EVALUATION_SHADER) + std::cerr << "tessellation evaluation shader"; +#endif + std::cerr << " \"" << name << "\":" << std::endl; + std::cerr << shader_string << std::endl << std::endl; + glGetShaderInfoLog(id, 512, nullptr, buffer); + std::cerr << "Error: " << std::endl << buffer << std::endl; + throw std::runtime_error("Shader compilation failed!"); + } + + return id; +} + +bool GLShader::initFromFiles (const std::string &name, + const std::string &vertex_fname, + const std::string &fragment_fname, + const std::string &geometry_fname) +{ + auto file_to_string = [](const std::string &filename) -> std::string + { + if (filename.empty()) + return ""; + std::ifstream t(filename); + return std::string((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + }; + + return init(name, + file_to_string(vertex_fname), + file_to_string(fragment_fname), + file_to_string(geometry_fname)); +} + +bool GLShader::init(const std::string &name, + const std::string &vertex_str, + const std::string &fragment_str, + const std::string &geometry_str, + bool failSilently) +{ + std::string defines; + for (auto def : mDefinitions) + defines += std::string("#define ") + def.first + std::string(" ") + def.second + "\n"; + + mName = name; + mVertexShader = + createShader_helper(GL_VERTEX_SHADER, name, defines, vertex_str, failSilently); + mGeometryShader = + createShader_helper(GL_GEOMETRY_SHADER, name, defines, geometry_str, failSilently); + mFragmentShader = + createShader_helper(GL_FRAGMENT_SHADER, name, defines, fragment_str, failSilently); + + //only relevant for failSilently + if (!mVertexShader || !mFragmentShader) + return false; + if (!geometry_str.empty() && !mGeometryShader) + return false; + + mProgramShader = glCreateProgram(); + + glAttachShader(mProgramShader, mVertexShader); + glAttachShader(mProgramShader, mFragmentShader); + + if (mGeometryShader) + glAttachShader(mProgramShader, mGeometryShader); + + glLinkProgram(mProgramShader); + + GLint status; + glGetProgramiv(mProgramShader, GL_LINK_STATUS, &status); + + if (status != GL_TRUE) + { + if (failSilently) + return false; + char buffer[512]; + glGetProgramInfoLog(mProgramShader, 512, nullptr, buffer); + std::cerr << "Linker error (" << mName << "): " << std::endl << buffer << std::endl; + mProgramShader = 0; + throw std::runtime_error("Shader linking failed!"); + } + + return true; +} + +#ifdef HAVE_TESSELLATION +bool GLShader::initWithTessellation(const std::string &name, + const std::string &vertex_str, + const std::string &tessellation_control_str, + const std::string &tessellation_eval_str, + const std::string &fragment_str, + const std::string &geometry_str, + bool failSilently) +{ + std::string defines; + for (auto def : mDefinitions) + defines += std::string("#define ") + def.first + std::string(" ") + def.second + "\n"; + + mName = name; + mVertexShader = + createShader_helper(GL_VERTEX_SHADER, name, defines, vertex_str, failSilently); +#ifdef HAVE_TESSELLATION + mTessellationControlShader = + createShader_helper(GL_TESS_CONTROL_SHADER, name, defines, tessellation_control_str, failSilently); + mTessellationEvalShader = + createShader_helper(GL_TESS_EVALUATION_SHADER, name, defines, tessellation_eval_str, failSilently); +#endif + mGeometryShader = + createShader_helper(GL_GEOMETRY_SHADER, name, defines, geometry_str, failSilently); + mFragmentShader = + createShader_helper(GL_FRAGMENT_SHADER, name, defines, fragment_str, failSilently); + + //only relevant for failSilently + if (!mVertexShader || !mFragmentShader || !mTessellationControlShader || !mTessellationEvalShader) + return false; + if (!geometry_str.empty() && !mGeometryShader) + return false; + + mProgramShader = glCreateProgram(); + + glAttachShader(mProgramShader, mVertexShader); + glAttachShader(mProgramShader, mTessellationControlShader); + glAttachShader(mProgramShader, mTessellationEvalShader); + glAttachShader(mProgramShader, mFragmentShader); + + if (mGeometryShader) + glAttachShader(mProgramShader, mGeometryShader); + + glLinkProgram(mProgramShader); + + GLint status; + glGetProgramiv(mProgramShader, GL_LINK_STATUS, &status); + + if (status != GL_TRUE) + { + if (failSilently) + return false; + char buffer[512]; + glGetProgramInfoLog(mProgramShader, 512, nullptr, buffer); + std::cerr << "Linker error (" << mName << "): " << std::endl << buffer << std::endl; + mProgramShader = 0; + throw std::runtime_error("Shader linking failed!"); + } + + return true; +} +#endif + +void GLShader::bind() +{ + glUseProgram(mProgramShader); + _currentProgram = this; +} + +GLint GLShader::attrib(const std::string &name, bool warn) const +{ + GLint id = glGetAttribLocation(mProgramShader, name.c_str()); + if (id == -1 && warn) + std::cerr << mName << ": warning: did not find attrib " << name << std::endl; + return id; +} + +GLint GLShader::uniform(const std::string &name, bool warn) const +{ + GLint id = glGetUniformLocation(mProgramShader, name.c_str()); + if (id == -1 && warn) + std::cerr << mName << ": warning: did not find uniform " << name << std::endl; + return id; +} \ No newline at end of file diff --git a/common/src/gui/GLVertexArray.cpp b/common/src/gui/GLVertexArray.cpp new file mode 100644 index 0000000..df0632e --- /dev/null +++ b/common/src/gui/GLVertexArray.cpp @@ -0,0 +1,44 @@ +/* + This file is part of NSEssentials. + + Use of this source code is granted via a BSD-style license, which can be found + in License.txt in the repository root. + + @author Nico Schertler +*/ + +#include "gui/GLVertexArray.h" + +using namespace nse::gui; + +GLVertexArray::GLVertexArray() + : vaoId(0) +{ +} + +GLVertexArray::~GLVertexArray() +{ + if (vaoId > 0) + glDeleteVertexArrays(1, &vaoId); +} + +void GLVertexArray::generate() +{ + if (vaoId == 0) + glGenVertexArrays(1, &vaoId); +} + +void GLVertexArray::bind() const +{ + glBindVertexArray(vaoId); +} + +void GLVertexArray::unbind() const +{ + glBindVertexArray(0); +} + +bool GLVertexArray::valid() const +{ + return vaoId != 0; +} \ No newline at end of file diff --git a/common/src/gui/ShaderPool.cpp b/common/src/gui/ShaderPool.cpp new file mode 100644 index 0000000..b7cc80a --- /dev/null +++ b/common/src/gui/ShaderPool.cpp @@ -0,0 +1,24 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include +#include "glsl.h" + +ShaderPool* ShaderPool::_instance(nullptr); +ShaderPool* ShaderPool::Instance() +{ + if (_instance == nullptr) + { + _instance = new ShaderPool(); + _instance->CompileShaders(); + } + + return _instance; +} + +void ShaderPool::CompileShaders() +{ + meshShader.init("Mesh Shader", std::string((char*)mesh_vert, mesh_vert_size), std::string((char*)mesh_frag, mesh_frag_size)); + simpleShader.init("Simple Shader", std::string((char*)simple_vert, simple_vert_size), std::string((char*)simple_frag, simple_frag_size)); +} \ No newline at end of file diff --git a/common/src/gui/SliderHelper.cpp b/common/src/gui/SliderHelper.cpp new file mode 100644 index 0000000..7ae96cb --- /dev/null +++ b/common/src/gui/SliderHelper.cpp @@ -0,0 +1,89 @@ +#include +#include +#include + +using namespace nse::gui; + +nanogui::Slider* nse::gui::AddWidgetWithSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::Widget*& outWidget) +{ + outWidget = new nanogui::Widget(parent); + outWidget->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); + new nanogui::Label(outWidget, caption); + + auto slider = new nanogui::Slider(outWidget); + slider->setFixedWidth(100); + slider->setValue(defaultValue); + slider->setRange(range); + + return slider; +} + +nanogui::Slider* nse::gui::AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue) +{ + nanogui::Widget* widget; + return AddWidgetWithSlider(parent, caption, range, defaultValue, widget); +} + + +nanogui::Slider* nse::gui::AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::TextBox*& out_label) +{ + nanogui::Widget* widget; + auto slider = AddWidgetWithSlider(parent, caption, range, defaultValue, widget); + + out_label = new nanogui::TextBox(widget); + out_label->setFixedSize(Eigen::Vector2i(60, 25)); + + return slider; +} + +nanogui::Slider* nse::gui::AddLabeledSliderWithDefaultDisplay(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, std::streamsize displayPrecision) +{ + nanogui::TextBox* txt; + auto sld = AddLabeledSlider(parent, caption, range, defaultValue, txt); + sld->setCallback([txt, displayPrecision](float value) { + std::stringstream ss; + ss.precision(displayPrecision); + ss << std::fixed << value; + txt->setValue(ss.str()); + }); + + sld->callback()(sld->value()); + + return sld; +} + +VectorInput::VectorInput(nanogui::Widget* parent, const std::string& prefix, const Eigen::Vector3f& min, const Eigen::Vector3f& max, const Eigen::Vector3f& current, std::function callback) +{ + sldX = nse::gui::AddLabeledSliderWithDefaultDisplay(parent, prefix + " X", std::make_pair(min.x(), max.x()), current.x(), 2); + sldY = nse::gui::AddLabeledSliderWithDefaultDisplay(parent, prefix + " Y", std::make_pair(min.y(), max.y()), current.y(), 2); + sldZ = nse::gui::AddLabeledSliderWithDefaultDisplay(parent, prefix + " Z", std::make_pair(min.z(), max.z()), current.z(), 2); + auto oldXCb = sldX->callback(); + auto oldYCb = sldY->callback(); + auto oldZCb = sldZ->callback(); + auto update = [callback, this]() { + auto value = Value(); + callback(value); + }; + sldX->setCallback([oldXCb, update](float value) {oldXCb(value); update(); }); + sldY->setCallback([oldYCb, update](float value) {oldYCb(value); update(); }); + sldZ->setCallback([oldZCb, update](float value) {oldZCb(value); update(); }); +} + +Eigen::Vector3f VectorInput::Value() const +{ + return Eigen::Vector3f(sldX->value(), sldY->value(), sldZ->value()); +} + +void VectorInput::SetBounds(const Eigen::Vector3f& lower, const Eigen::Vector3f& upper) +{ + sldX->setRange(std::make_pair(lower.x(), upper.x())); + sldY->setRange(std::make_pair(lower.y(), upper.y())); + sldZ->setRange(std::make_pair(lower.z(), upper.z())); +} + +void VectorInput::SetValue(const Eigen::Vector3f& value) +{ + sldX->setValue(value.x()); sldX->callback()(sldX->value()); + sldY->setValue(value.y()); sldY->callback()(sldY->value()); + sldZ->setValue(value.z()); sldZ->callback()(sldZ->value()); +} \ No newline at end of file diff --git a/common/src/util/GLDebug.cpp b/common/src/util/GLDebug.cpp new file mode 100644 index 0000000..dd8699e --- /dev/null +++ b/common/src/util/GLDebug.cpp @@ -0,0 +1,136 @@ +#include "util/GLDebug.h" +#include + +std::unordered_set nse::util::GLDebug::ignoredIds; + +std::string FormatDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity, const char* msg) +{ + std::stringstream stringStream; + std::string sourceString; + std::string typeString; + std::string severityString; + + // The AMD variant of this extension provides a less detailed classification of the error, + // which is why some arguments might be "Unknown". + switch (source) { + case GL_DEBUG_CATEGORY_API_ERROR_AMD: + case GL_DEBUG_SOURCE_API: { + sourceString = "API"; + break; + } + case GL_DEBUG_CATEGORY_APPLICATION_AMD: + case GL_DEBUG_SOURCE_APPLICATION: { + sourceString = "Application"; + break; + } + case GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD: + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: { + sourceString = "Window System"; + break; + } + case GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD: + case GL_DEBUG_SOURCE_SHADER_COMPILER: { + sourceString = "Shader Compiler"; + break; + } + case GL_DEBUG_SOURCE_THIRD_PARTY: { + sourceString = "Third Party"; + break; + } + case GL_DEBUG_CATEGORY_OTHER_AMD: + case GL_DEBUG_SOURCE_OTHER: { + sourceString = "Other"; + break; + } + default: { + sourceString = "Unknown"; + break; + } + } + + switch (type) { + case GL_DEBUG_TYPE_ERROR: { + typeString = "Error"; + break; + } + case GL_DEBUG_CATEGORY_DEPRECATION_AMD: + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: { + typeString = "Deprecated Behavior"; + break; + } + case GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD: + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: { + typeString = "Undefined Behavior"; + break; + } + case GL_DEBUG_TYPE_PORTABILITY_ARB: { + typeString = "Portability"; + break; + } + case GL_DEBUG_CATEGORY_PERFORMANCE_AMD: + case GL_DEBUG_TYPE_PERFORMANCE: { + typeString = "Performance"; + break; + } + case GL_DEBUG_CATEGORY_OTHER_AMD: + case GL_DEBUG_TYPE_OTHER: { + typeString = "Other"; + break; + } + default: { + typeString = "Unknown"; + break; + } + } + + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: { + severityString = "High"; + break; + } + case GL_DEBUG_SEVERITY_MEDIUM: { + severityString = "Medium"; + break; + } + case GL_DEBUG_SEVERITY_LOW: { + severityString = "Low"; + break; + } + default: { + severityString = "Unknown"; + break; + } + } + + stringStream << "OpenGL Error: " << msg; + stringStream << " [Source = " << sourceString; + stringStream << ", Type = " << typeString; + stringStream << ", Severity = " << severityString; + stringStream << ", ID = " << id << "]"; + + return stringStream.str(); +} + +void APIENTRY DebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +{ + const std::unordered_set* ignoredIds = static_cast*>(userParam); + + if(ignoredIds->find(id) != ignoredIds->end()) + return; + + + std::string error = FormatDebugOutput(source, type, id, severity, message); + std::cout << error << std::endl; +} + +void nse::util::GLDebug::IgnoreGLError(GLuint errorId) +{ + ignoredIds.insert(errorId); +} + +void nse::util::GLDebug::SetupDebugCallback() +{ + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(DebugCallback, &ignoredIds); +} \ No newline at end of file diff --git a/common/src/util/OpenMeshUtils.cpp b/common/src/util/OpenMeshUtils.cpp new file mode 100644 index 0000000..1fe91dc --- /dev/null +++ b/common/src/util/OpenMeshUtils.cpp @@ -0,0 +1,208 @@ +#include "util/OpenMeshUtils.h" + +#include +#include + +MeshRenderer::MeshRenderer(const HEMesh& mesh) + : mesh(mesh), indexCount(0), + positionBuffer(nse::gui::VertexBuffer), normalBuffer(nse::gui::VertexBuffer), colorBuffer(nse::gui::VertexBuffer), texCoordBuffer(nse::gui::VertexBuffer), + indexBuffer(nse::gui::IndexBuffer), + texCoordBuffer4D(nse::gui::VertexBuffer), indexBufferTexCoords(nse::gui::IndexBuffer) +{ + vao.generate(); + vaoTexCoords.generate(); + + Update(); +} + +//EmitVertexFunctor: void(const HEMesh::HalfedgeHandle[3]) //the to-vertices of the halfedges are the triangle corners +template +void TriangulateMeshFace(HEMesh::FaceHandle f, const HEMesh& mesh, EmitTriangleFunctor&& emitTriangle) +{ + OpenMesh::HalfedgeHandle base; + for (auto h : mesh.fh_range(f)) + { + if (base.idx() == -1) + { + base = h; + continue; + } + auto nextH = mesh.next_halfedge_handle(h); + if (nextH == base) + break; + else + { + HEMesh::HalfedgeHandle triangle[3] = { base, h, nextH }; + std::forward(emitTriangle)(triangle); + } + } +} + +void MeshRenderer::Update() +{ + if (mesh.n_vertices() == 0) + return; + + ShaderPool::Instance()->meshShader.bind(); + vao.bind(); + + std::vector positions; + std::vector normals; + std::vector uvs; + positions.reserve(mesh.n_vertices()); + normals.reserve(mesh.n_vertices()); + if(mesh.has_vertex_texcoords2D()) + uvs.reserve(mesh.n_vertices()); + for (auto v : mesh.vertices()) + { + positions.push_back(ToEigenVector4(mesh.point(v))); + OpenMesh::Vec3f n; + mesh.calc_vertex_normal_correct(v, n); + normals.push_back(ToEigenVector4(n, 0)); + if (mesh.has_vertex_texcoords2D()) + uvs.push_back(ToEigenVector(mesh.texcoord2D(v))); + } + positionBuffer.uploadData(positions).bindToAttribute("position"); + normalBuffer.uploadData(normals).bindToAttribute("normal"); + if (mesh.has_vertex_texcoords2D()) + texCoordBuffer.uploadData(uvs).bindToAttribute("texCoords"); + + std::vector indices; + indices.reserve(mesh.n_faces() * 3); + for (auto f : mesh.faces()) + { + TriangulateMeshFace(f, mesh, [&](const HEMesh::HalfedgeHandle h[3]) + { + indices.push_back(mesh.to_vertex_handle(h[0]).idx()); + indices.push_back(mesh.to_vertex_handle(h[1]).idx()); + indices.push_back(mesh.to_vertex_handle(h[2]).idx()); + }); + } + indexBuffer.uploadData(sizeof(uint32_t) * (uint32_t)indices.size(), indices.data()); + indexCount = (unsigned int)indices.size(); + + vao.unbind(); + + hasColor = false; + + UpdateTextureMapBuffers(); +} + +void MeshRenderer::UpdateWithPerFaceColor(OpenMesh::FPropHandleT colorProperty) +{ + if (mesh.n_vertices() == 0) + return; + + ShaderPool::Instance()->meshShader.bind(); + vao.bind(); + + std::vector positions; + std::vector normals; + std::vector colors; + std::vector uvs; + positions.reserve(3 * mesh.n_faces()); + normals.reserve(3 * mesh.n_faces()); + colors.reserve(3 * mesh.n_faces()); + if (mesh.has_vertex_texcoords2D()) + uvs.reserve(3 * mesh.n_faces()); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + for (auto f : mesh.faces()) + { + TriangulateMeshFace(f, mesh, [&](const HEMesh::HalfedgeHandle h[3]) + { + for (int i = 0; i < 3; ++i) + { + auto v = mesh.to_vertex_handle(h[i]); + positions.push_back(ToEigenVector4(mesh.point(v))); + OpenMesh::Vec3f n; + mesh.calc_vertex_normal_correct(v, n); + normals.push_back(ToEigenVector4(n, 0)); + colors.push_back(mesh.property(colorProperty, f)); + if (mesh.has_vertex_texcoords2D()) + uvs.push_back(ToEigenVector(mesh.texcoord2D(v))); + } + }); + } + positionBuffer.uploadData(positions).bindToAttribute("position"); + normalBuffer.uploadData(normals).bindToAttribute("normal"); + colorBuffer.uploadData(colors).bindToAttribute("color"); + if (mesh.has_vertex_texcoords2D()) + texCoordBuffer.uploadData(uvs).bindToAttribute("texCoords"); + + indexCount = (unsigned int)positions.size(); + + vao.unbind(); + + hasColor = true; + + UpdateTextureMapBuffers(); +} + +void MeshRenderer::UpdateTextureMapBuffers() +{ + if (!mesh.has_vertex_texcoords2D()) + return; + + ShaderPool::Instance()->simpleShader.bind(); + vaoTexCoords.bind(); + + std::vector positions; + positions.reserve(mesh.n_vertices()); + + for (auto v : mesh.vertices()) + positions.push_back(ToEigenVector4(mesh.texcoord2D(v))); + + texCoordBuffer4D.uploadData(positions).bindToAttribute("position"); + + std::vector indices; + indices.reserve(mesh.n_edges() * 2); + for (auto e : mesh.edges()) + { + auto h = mesh.halfedge_handle(e, 0); + indices.push_back(mesh.from_vertex_handle(h).idx()); + indices.push_back(mesh.to_vertex_handle(h).idx()); + } + indexBufferTexCoords.uploadData(sizeof(uint32_t) * (uint32_t)indices.size(), indices.data()); + indexCountTexCoords = (unsigned int)indices.size(); + + vaoTexCoords.unbind(); +} + +void MeshRenderer::Render(const Eigen::Matrix4f& view, const Eigen::Matrix4f& projection, bool flatShading, bool withTexCoords, const Eigen::Vector4f& color) const +{ + if (indexCount == 0) + return; + + auto& shader = ShaderPool::Instance()->meshShader; + shader.bind(); + shader.setUniform("view", view); + shader.setUniform("proj", projection); + shader.setUniform("flatShading", flatShading ? 1 : 0); + shader.setUniform("perVertexColor", hasColor ? 1 : 0); + shader.setUniform("visualizeTexCoords", withTexCoords ? 1 : 0); + shader.setUniform("color", color); + + vao.bind(); + if (hasColor) + glDrawArrays(GL_TRIANGLES, 0, indexCount); + else + glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0); + vao.unbind(); +} + +void MeshRenderer::RenderTextureMap(const Eigen::Matrix4f& projection, const Eigen::Vector4f& color) const +{ + if (indexCountTexCoords == 0) + return; + + auto& shader = ShaderPool::Instance()->simpleShader; + shader.bind(); + shader.setUniform("mvp", projection); + shader.setUniform("color", color); + + vaoTexCoords.bind(); + glDrawElements(GL_LINES, indexCountTexCoords, GL_UNSIGNED_INT, 0); + vaoTexCoords.unbind(); +} \ No newline at end of file diff --git a/common/src/util/UnionFind.cpp b/common/src/util/UnionFind.cpp new file mode 100644 index 0000000..2f83b6f --- /dev/null +++ b/common/src/util/UnionFind.cpp @@ -0,0 +1,133 @@ +#include "util/UnionFind.h" + +#include +#include +#include +#include + +using namespace nse::util; + +void UnionFind::SaveToFile(const char* filename) const +{ + FILE* file = fopen(filename, "wb"); + uint64_t entries = size(); + fwrite(&entries, sizeof(uint64_t), 1, file); + fwrite(&parentIndices[0], sizeof(index_t), parentIndices.size(), file); + fwrite(&ranks[0], sizeof(unsigned int), ranks.size(), file); + fclose(file); +} + +std::size_t UnionFind::size() const { return parentIndices.size(); } + +// Loads the entire structure from a file. Existing data in the structure is overridden. +void UnionFind::LoadFromFile(const char* filename) +{ + uint64_t entries; + + FILE* file = fopen(filename, "rb"); + if(fread(&entries, sizeof(uint64_t), 1, file) != 1) + throw std::runtime_error("Cannot read enough data from file"); + parentIndices.resize(entries); + ranks.resize(entries); + if(fread(&parentIndices[0], sizeof(index_t), entries, file) != entries) + throw std::runtime_error("Cannot read enough data from file"); + if(fread(&ranks[0], sizeof(unsigned int), entries, file) != entries) + throw std::runtime_error("Cannot read enough data from file"); + fclose(file); +} + +// Adds an item to the structure +void UnionFind::AddItem() +{ + parentIndices.push_back((index_t)parentIndices.size()); + ranks.push_back(0); +} + +void UnionFind::AddItems(std::size_t count) +{ + auto oldCount = parentIndices.size(); + parentIndices.resize(parentIndices.size() + count); + for (index_t i = static_cast(oldCount); i < parentIndices.size(); ++i) + parentIndices[i] = i; + ranks.resize(ranks.size() + count); +} + +void UnionFind::Clear() +{ + parentIndices.clear(); + ranks.clear(); +} + +// Finds the set representative for a given entry. Two entries are in the same set +// iff they have the same set representative. +UnionFind::index_t UnionFind::GetRepresentative(index_t index) +{ + //Find the root + index_t current = index; + while (parentIndices[current] != current) + current = parentIndices[current]; + + //Path compression + index_t root = current; + current = index; + while (parentIndices[current] != current) + { + index_t i = current; + current = parentIndices[current]; + parentIndices[i] = root; + } + + return root; +} + +// Merges the sets of the two specified entries. +UnionFind::index_t UnionFind::Merge(index_t i1, index_t i2) +{ + index_t rep1 = GetRepresentative(i1); + index_t rep2 = GetRepresentative(i2); + if (rep1 == rep2) + return rep1; + + //Union by rank + unsigned int rank1 = ranks[rep1]; + unsigned int rank2 = ranks[rep2]; + + if (rank1 < rank2) + { + ConcreteMerge(rep2, rep1); + return rep2; + } + else if (rank2 < rank1) + { + ConcreteMerge(rep1, rep2); + return rep1; + } + else + { + ConcreteMerge(rep1, rep2); + ++ranks[rep1]; + return rep1; + } +} + +void UnionFind::MergeWithPredefinedRoot(index_t newRoot, index_t i) +{ + assert(GetRepresentative(newRoot) == newRoot); + + index_t rep2 = GetRepresentative(i); + + if (newRoot == rep2) + return; + + unsigned int rank1 = ranks[newRoot]; + unsigned int rank2 = ranks[rep2]; + + if (rank1 == rank2) + ++ranks[newRoot]; + ConcreteMerge(newRoot, i); +} + +void UnionFind::ConcreteMerge(index_t newRoot, index_t child) +{ + parentIndices[child] = newRoot; +} \ No newline at end of file diff --git a/exercise1/CMakeLists.txt b/exercise1/CMakeLists.txt new file mode 100644 index 0000000..bd2faaa --- /dev/null +++ b/exercise1/CMakeLists.txt @@ -0,0 +1,13 @@ +set(GLSL_FILES shader.vert shader.frag) + +ProcessGLSLFiles(GLSL_FILES) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) + +add_executable(Exercise1 MACOSX_BUNDLE + glsl.cpp + src/main.cpp + src/Viewer.cpp include/Viewer.h + ${GLSL_FILES}) + +target_link_libraries(Exercise1 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise1/glsl/shader.frag b/exercise1/glsl/shader.frag new file mode 100644 index 0000000..21e616c --- /dev/null +++ b/exercise1/glsl/shader.frag @@ -0,0 +1,24 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved +#version 130 + +in vec4 fragment_color; + +out vec4 color; + + + +void main(void) +{ + color = fragment_color * 0.5 + vec4(0.5); + + /**** Begin of tasks *** + - 1.2.5 + Implement the pseudo-code for calculating the julia fractal at a point. + To get the point you can declare a new "in" variable which contains the + position and just use the X- and Y- value. + + *** End of tasks ***/ + +} \ No newline at end of file diff --git a/exercise1/glsl/shader.vert b/exercise1/glsl/shader.vert new file mode 100644 index 0000000..d52639a --- /dev/null +++ b/exercise1/glsl/shader.vert @@ -0,0 +1,31 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved +#version 130 + +in vec4 in_position; + +out vec4 fragment_color; + + + + +void main(void) +{ + gl_Position = in_position; + fragment_color = in_position; + + /* - 1.2.2 (b) + * Declare a new "in" variable with the name "in_color". Instead of setting + * "fragment_color" to the position, set it to "in_color. */ + + /* - 1.2.4 (a) + * Declare two new "uniform" variables with the type "mat4" (above the main function) + * that store the modelview and projection matrix. To apply the transformations + * multiply the value of "in_position" before setting "gl_Position". */ + + /* - 1.2.5 + * The algorithm to calculate the julia fractal needs a position as input. + * Declare another "out" variable and set it to the untransformed input + * position. */ +} \ No newline at end of file diff --git a/exercise1/include/Viewer.h b/exercise1/include/Viewer.h new file mode 100644 index 0000000..99810f3 --- /dev/null +++ b/exercise1/include/Viewer.h @@ -0,0 +1,42 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include + +class Viewer : public nse::gui::AbstractViewer +{ +public: + Viewer(); + + void drawContents(); + +private: + void SetupGUI(); + + Eigen::Matrix4f modelViewMatrix, projectionMatrix; + + //GUI Elements for the various options + nanogui::CheckBox* chkHasFaceCulling; //Shall back face culling be activated? + nanogui::CheckBox* chkHasDepthTesting; //Shall depth testing be activated? + + nanogui::Slider* sldJuliaCX; //Seed for the Julia fractal + nanogui::Slider* sldJuliaCY; + nanogui::Slider* sldJuliaZoom; //Zoom factor for the Julia fractal + + // The following variables hold OpenGL object IDs + GLuint vertex_shader_id, // ID of the vertex shader + fragment_shader_id, // ID of the fragment shader + program_id, // ID of the shader program + vertex_array_id, // ID of the vertex array + position_buffer_id, // ID of the position buffer + color_buffer_id, // ID of the color buffer + uv_map_buffer_id; // ID of the uv_map + + // Read, Compile and link the shader codes to a shader program + void CreateShaders(); + // Create and define the vertex array and add a number of vertex buffers + void CreateVertexBuffers(); +}; diff --git a/exercise1/src/Viewer.cpp b/exercise1/src/Viewer.cpp new file mode 100644 index 0000000..64488b1 --- /dev/null +++ b/exercise1/src/Viewer.cpp @@ -0,0 +1,183 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Viewer.h" + +#include +#include +#include + +#include + +#include + +#include "glsl.h" + +Viewer::Viewer() + : AbstractViewer("CG1 Exercise 1") +{ + SetupGUI(); + + CreateShaders(); + CreateVertexBuffers(); + + modelViewMatrix.setIdentity(); + projectionMatrix.setIdentity(); + + camera().FocusOnBBox(nse::math::BoundingBox(Eigen::Vector3f(-1, -1, -1), Eigen::Vector3f(1, 1, 1))); +} + +void Viewer::SetupGUI() +{ + auto mainWindow = SetupMainWindow(); + + //Create GUI elements for the various options + chkHasDepthTesting = new nanogui::CheckBox(mainWindow, "Perform Depth Testing"); + chkHasDepthTesting->setChecked(true); + + chkHasFaceCulling = new nanogui::CheckBox(mainWindow, "Perform backface Culling"); + chkHasFaceCulling->setChecked(true); + + sldJuliaCX = nse::gui::AddLabeledSliderWithDefaultDisplay(mainWindow, "JuliaC.X", std::make_pair(-1.0f, 1.0f), 0.45f, 2); + sldJuliaCY = nse::gui::AddLabeledSliderWithDefaultDisplay(mainWindow, "JuliaC.Y", std::make_pair(-1.0f, 1.0f), -0.3f, 2); + sldJuliaZoom = nse::gui::AddLabeledSliderWithDefaultDisplay(mainWindow, "Julia Zoom", std::make_pair(0.01f, 10.0f), 1.0f, 2); + + performLayout(); +} + +// Create and define the vertex array and add a number of vertex buffers +void Viewer::CreateVertexBuffers() +{ + /*** Begin of task 1.2.3 *** + Fill the positions-array and your color array with 12 rows, each + containing 4 entries, to define a tetrahedron. */ + + // Define 3 vertices for one face + GLfloat positions[] = { + 0, 1, 0, 1, + -1, -1, 0, 1, + 1, -1, 0, 1 + }; + + + + + + // Generate the vertex array + glGenVertexArrays(1, &vertex_array_id); + glBindVertexArray(vertex_array_id); + + // Generate a position buffer to be appended to the vertex array + glGenBuffers(1, &position_buffer_id); + // Bind the buffer for subsequent settings + glBindBuffer(GL_ARRAY_BUFFER, position_buffer_id); + // Supply the position data + glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); + // The buffer shall now be linked to the shader attribute + // "in_position". First, get the location of this attribute in + // the shader program + GLuint vid = glGetAttribLocation(program_id, "in_position"); + // Enable this vertex attribute array + glEnableVertexAttribArray(vid); + // Set the format of the data to match the type of "in_position" + glVertexAttribPointer(vid, 4, GL_FLOAT, GL_FALSE, 0, 0); + + /*** Begin of task 1.2.2 (a) *** + Create another buffer that will store color information. This works nearly + similar to the code above that creates the position buffer. Store the buffer + id into the variable "color_buffer_id" and bind the color buffer to the + shader variable "in_color". + + + /*** End of task 1.2.2 (a) ***/ + + + + // Unbind the vertex array to leave OpenGL in a clean state + glBindVertexArray(0); +} + +//Checks if the given shader has been compiled successfully. Otherwise, prints an +//error message and throws an exception. +// shaderId - the id of the shader object +// name - a human readable name for the shader that is printed together with the error +void CheckShaderCompileStatus(GLuint shaderId, std::string name) +{ + GLint status; + glGetShaderiv(shaderId, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + char buffer[512]; + std::cerr << "Error while compiling shader \"" << name << "\":" << std::endl; + glGetShaderInfoLog(shaderId, 512, nullptr, buffer); + std::cerr << "Error: " << std::endl << buffer << std::endl; + throw std::runtime_error("Shader compilation failed!"); + } +} + +// Read, Compile and link the shader codes to a shader program +void Viewer::CreateShaders() +{ + std::string vs((char*)shader_vert, shader_vert_size); + const char *vertex_content = vs.c_str(); + + std::string fs((char*)shader_frag, shader_frag_size); + const char *fragment_content = fs.c_str(); + + /*** Begin of task 1.2.1 *** + Use the appropriate OpenGL commands to create a shader object for + the vertex shader, set the source code and let it compile. Store the + ID of this shader object in the variable "vertex_shader_id". Repeat + for the fragment shader. Store the ID in the variable "fragment_shader_id. + Finally, create a shader program with its handle stored in "program_id", + attach both shader objects and link them. For error checking, you can + use the method "CheckShaderCompileStatus()" after the call to glCompileShader(). + */ + /*** End of task 1.2.1 ***/ +} + +void Viewer::drawContents() +{ + Eigen::Vector2f juliaC(sldJuliaCX->value(), sldJuliaCY->value()); + float juliaZoom = sldJuliaZoom->value(); + + //Get the transform matrices + camera().ComputeCameraMatrices(modelViewMatrix, projectionMatrix); + + // If has_faceculling is set then enable backface culling + // and disable it otherwise + if (chkHasFaceCulling->checked()) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + + // If has_depthtesting is set then enable depth testing + // and disable it otherwise + if (chkHasDepthTesting->checked()) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + + // Activate the shader program + glUseProgram(program_id); + + /*** Begin of task 1.2.4 (b) *** + Set the shader variables for the modelview and projection matrix. + First, find the location of these variables using glGetUniformLocation and + then set them with the command glUniformMatrix4fv. + */ + + // Bind the vertex array + glBindVertexArray(vertex_array_id); + // Draw the bound vertex array. Start at element 0 and draw 3 vertices + glDrawArrays(GL_TRIANGLES, 0, 3); + + /*** End of task 1.2.4 (b) ***/ + + // Unbind the vertex array + glBindVertexArray(0); + // Deactivate the shader program + glUseProgram(0); +} \ No newline at end of file diff --git a/exercise1/src/main.cpp b/exercise1/src/main.cpp new file mode 100644 index 0000000..e01821b --- /dev/null +++ b/exercise1/src/main.cpp @@ -0,0 +1,36 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include + +#include + +#include "Viewer.h" + +int main(int argc, char* argv[]) +{ + nanogui::init(); + + { + nanogui::ref viewer = new Viewer(); + viewer->setVisible(true); + + nse::util::GLDebug::SetupDebugCallback(); + nse::util::GLDebug::IgnoreGLError(131185); //buffer usage info + + try + { + nanogui::mainloop(); + } + catch (std::runtime_error& e) + { + std::cerr << e.what() << std::endl; + } + + } + + nanogui::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/exercise2/CMakeLists.txt b/exercise2/CMakeLists.txt new file mode 100644 index 0000000..6c9fb4d --- /dev/null +++ b/exercise2/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) + +add_executable(Exercise2 MACOSX_BUNDLE + src/main.cpp + src/Viewer.cpp include/Viewer.h + src/Primitives.cpp include/Primitives.h + src/SurfaceArea.cpp include/SurfaceArea.h + src/Volume.cpp include/Volume.h + src/ShellExtraction.cpp include/ShellExtraction.h + src/Smoothing.cpp include/Smoothing.h + src/Stripification.cpp include/Stripification.h + include/sample_set.h) + +target_link_libraries(Exercise2 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise2/include/Primitives.h b/exercise2/include/Primitives.h new file mode 100644 index 0000000..10d7c9d --- /dev/null +++ b/exercise2/include/Primitives.h @@ -0,0 +1,38 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include "util/OpenMeshUtils.h" + +//create a mesh containing a single quad +void CreateQuad(HEMesh& m); + +//create a unit coube mesh +void CreateCube(HEMesh& m); + +//create a regular tetraeder mesh +void CreateTetrahedron(HEMesh& m, float a = 1); + +//create a disc mesh +void CreateDisk(HEMesh& m, float radius, int slices); + +//create a cylinder mesh +void CreateCylinder(HEMesh& m, float radius, float height, int stacks, int slices); + +//create a sphere mesh +void CreateSphere(HEMesh& m, float radius, int slices, int stacks); + +//create a torus mesh +void CreateTorus(HEMesh& m, float r, float R, int nsides, int rings); + +//creates an icosaeder mesh in m +// radius is the radius of the circum sphere +void CreateIcosahedron(HEMesh& m, float radius); + +//creates an octaeder mesh +// radius is the radius of the circum sphere +void CreateOctahedron(HEMesh& m, float radius); + +//create a unit arrow +void CreateUnitArrow(HEMesh& m, float stem_radius = 0.04, float head_radius = 0.1, float stem_height = 0.8, int slices = 30, int stem_stacks = 1); diff --git a/exercise2/include/ShellExtraction.h b/exercise2/include/ShellExtraction.h new file mode 100644 index 0000000..6cf8c06 --- /dev/null +++ b/exercise2/include/ShellExtraction.h @@ -0,0 +1,11 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include "util/OpenMeshUtils.h" + +//Finds the connected components in the mesh. Writes the non-negative shell index into perFaceShellIndex +//and returns the number of shells. +unsigned int ExtractShells(HEMesh& m, OpenMesh::FPropHandleT perFaceShellIndex); \ No newline at end of file diff --git a/exercise2/include/Smoothing.h b/exercise2/include/Smoothing.h new file mode 100644 index 0000000..a650011 --- /dev/null +++ b/exercise2/include/Smoothing.h @@ -0,0 +1,12 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include "util/OpenMeshUtils.h" + +//Updates the vertex positions by Laplacian smoothing +void SmoothUniformLaplacian(HEMesh& m, float lambda, unsigned int iterations); + +void AddNoise(HEMesh& m); \ No newline at end of file diff --git a/exercise2/include/Stripification.h b/exercise2/include/Stripification.h new file mode 100644 index 0000000..30695ad --- /dev/null +++ b/exercise2/include/Stripification.h @@ -0,0 +1,11 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include "util/OpenMeshUtils.h" + +//Extracts triangle strips from the mesh, writes the Strip Id of each face in +//perFaceStripIdProperty, and returns the number of strips. +unsigned int ExtractTriStrips(HEMesh& m, OpenMesh::FPropHandleT perFaceStripIdProperty, unsigned int nTrials); \ No newline at end of file diff --git a/exercise2/include/SurfaceArea.h b/exercise2/include/SurfaceArea.h new file mode 100644 index 0000000..a9fba6c --- /dev/null +++ b/exercise2/include/SurfaceArea.h @@ -0,0 +1,12 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include "util/OpenMeshUtils.h" + +//computes the surface area of the halfedge mesh m by computing and summing up the +//areas of all polygonal faces +//method is not restricted to triangle meshes +float ComputeSurfaceArea(const HEMesh& m); \ No newline at end of file diff --git a/exercise2/include/Viewer.h b/exercise2/include/Viewer.h new file mode 100644 index 0000000..29d694b --- /dev/null +++ b/exercise2/include/Viewer.h @@ -0,0 +1,35 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include + +class Viewer : public nse::gui::AbstractViewer +{ +public: + Viewer(); + + void drawContents(); + +private: + void SetupGUI(); + void MeshUpdated(bool initNewMesh = false); + + void ColorMeshFromIds(); + + bool hasColors = false; + + nanogui::ComboBox* shadingBtn; + unsigned int smoothingIterations; + nanogui::Slider* sldSmoothingStrength; + unsigned int stripificationTrials; + + HEMesh polymesh; + MeshRenderer renderer; + + OpenMesh::FPropHandleT faceIdProperty; + OpenMesh::FPropHandleT faceColorProperty; +}; diff --git a/exercise2/include/Volume.h b/exercise2/include/Volume.h new file mode 100644 index 0000000..cd0802d --- /dev/null +++ b/exercise2/include/Volume.h @@ -0,0 +1,12 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include "util/OpenMeshUtils.h" + +//computes the surface area of the halfedge mesh m by computing and summing up the +//areas of all polygonal faces +//method is not restricted to triangle meshes +float ComputeVolume(const HEMesh& m); \ No newline at end of file diff --git a/exercise2/include/sample_set.h b/exercise2/include/sample_set.h new file mode 100644 index 0000000..e836c69 --- /dev/null +++ b/exercise2/include/sample_set.h @@ -0,0 +1,109 @@ +// +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) 2015-2017 CGV TU Dresden - All Rights Reserved +// +#pragma once +#include +#include +#include + + +/* +Usage: + +//C++0x Random Engine Mersenne Twister +std::mt19937 eng; + +//a set data structure with amortize constant time insertion, removal, and uniform random sampling from its elements +sample_set my_set; +my_set.reserve(3); + +my_set.insert(1); +my_set.insert(2); +my_set.insert(6); + +int rand_elem1 = my_set.sample(eng);//1,2 or 6 +my_set.remove(2); + +int rand_elem2 = my_set.sample(eng); //1 or 6 + +*/ + + +template +struct sample_set +{ + + typedef int element_index; + std::vector elements; + std::unordered_map index_lut; + + + //create empty set + sample_set(){} + + //reserve memory for n elements + void reserve(size_t n) + { + elements.reserve(n); + index_lut.rehash(n); + } + + //insert element elem into set + void insert(const T& elem) + { + //guard against duplicates + if (index_lut.find(elem) == index_lut.end()) + { + element_index idx = (element_index)elements.size(); + elements.push_back(elem); + index_lut[elem]=idx; + } + } + + //remove element elem from set + bool remove(const T& elem) + { + auto it = index_lut.find(elem); + if(it == index_lut.end()) + { + return false; + } + + int i = it->second; + + std::swap(elements[i],elements.back()); + elements.pop_back(); + + index_lut.erase(it); + if(unsigned(i) < elements.size()) + { + index_lut[elements[i]]=i; + } + return true; + } + + //draw a sample from set + template + const T& sample(Engine& eng) + { + int b = (element_index)(elements.size()-1); + std::uniform_int_distribution uniform_dist(0,b); + element_index idx = uniform_dist(eng); + return elements[idx]; + } + + //returns number of elements in set + size_t size() const + { + return elements.size(); + } + + //returns true if set is empty + bool empty() const + { + return elements.empty(); + } + +}; diff --git a/exercise2/src/Primitives.cpp b/exercise2/src/Primitives.cpp new file mode 100644 index 0000000..13252b8 --- /dev/null +++ b/exercise2/src/Primitives.cpp @@ -0,0 +1,307 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Primitives.h" +#include "util/OpenMeshUtils.h" + +void CreateQuad(HEMesh& mesh) +{ + mesh.clear(); + std::vector vhandles; + vhandles.push_back(mesh.add_vertex(OpenMesh::Vec3f(-0.5f, -0.5f, 0))); + vhandles.push_back(mesh.add_vertex(OpenMesh::Vec3f( 0.5f, -0.5f, 0))); + vhandles.push_back(mesh.add_vertex(OpenMesh::Vec3f( 0.5f, 0.5f, 0))); + vhandles.push_back(mesh.add_vertex(OpenMesh::Vec3f(-0.5f, 0.5f, 0))); + mesh.add_face(vhandles); +} + +void CreateCube(HEMesh& mesh) +{ + mesh.clear(); + /* Task 2.2.1 */ +} + +void CreateTetrahedron(HEMesh& mesh, float a) +{ + mesh.clear(); + + std::vector vhandles(4); + + vhandles[0] = mesh.add_vertex(OpenMesh::Vec3f(sqrt(3.0f)*a / 3.0f, 0, 0)); + vhandles[1] = mesh.add_vertex(OpenMesh::Vec3f(-sqrt(3.0f)*a / 6.0f, -a / 2.0f, 0)); + vhandles[2] = mesh.add_vertex(OpenMesh::Vec3f(-sqrt(3.0f)*a / 6.0f, a / 2.0f, 0)); + vhandles[3] = mesh.add_vertex(OpenMesh::Vec3f(0, 0, sqrt(6.0f)*a / 3.0f)); + + mesh.add_face(vhandles[0], vhandles[1], vhandles[2]); + mesh.add_face(vhandles[0], vhandles[2], vhandles[3]); + mesh.add_face(vhandles[0], vhandles[3], vhandles[1]); + mesh.add_face(vhandles[3], vhandles[2], vhandles[1]); +} + +void CreateDisk(HEMesh& mesh, float radius, int slices) +{ + mesh.clear(); + + std::vector vhandles(slices + 1); + vhandles[0] = mesh.add_vertex(OpenMesh::Vec3f(0, 0, 0)); + for (int i = 0; i < slices; i++) + { + float angle = -i * 2 * 3.14159f / slices; + vhandles[i + 1] = mesh.add_vertex(OpenMesh::Vec3f(sin(angle)*radius, cos(angle)*radius, 0)); + } + for (int i = 0; i < slices; i++) + mesh.add_face(vhandles[0], vhandles[1 + i%slices], vhandles[1 + (1 + i) % slices]); +} + +//create a cylinder mesh +void CreateCylinder(HEMesh& mesh, float radius, float height, int stacks, int slices) +{ + assert(slices >= 3 && stacks >= 1); + + mesh.clear(); + + int n = 2 + slices*(stacks + 1); + std::vector vhandles(n); + + vhandles[0] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, height, 0.0f)); + + int k = 1; + for (int i = 0; i < stacks + 1; i++) + { + float h = (stacks - i)*height / (stacks); + + for (int j = 0; j < slices; j++) + { + float angle2 = j*2.0f*3.14159f / (float)(slices); + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(cos(angle2)*radius, h, sin(angle2)*radius)); + + k++; + } + } + + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, 0.0f, 0.0f)); + + for (int i = 0; i < slices; i++) + { + mesh.add_face(vhandles[0], vhandles[1 + (1 + i) % slices], vhandles[1 + i%slices]); + + for (int j = 0; j < stacks; j++) + { + int a, b, c, d; + a = 1 + j*slices + (i) % slices; + b = 1 + j*slices + (1 + i) % slices; + c = 1 + (j + 1)*slices + (1 + i) % slices; + d = 1 + (j + 1)*slices + (i) % slices; + mesh.add_face(vhandles[a], vhandles[b], vhandles[c], vhandles[d]); + } + mesh.add_face(vhandles[vhandles.size() - 1], + vhandles[1 + (stacks)*slices + (i) % slices], + vhandles[1 + (stacks)*slices + (1 + i) % slices]); + } +} + +//create a sphere mesh +void CreateSphere(HEMesh& mesh, float radius, int slices, int stacks) +{ + assert(slices >= 3 && stacks >= 3); + + mesh.clear(); + + int n = slices*(stacks - 1) + 2; + std::vector vhandles(n); + + vhandles[0] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, radius, 0.0f)); + + int k = 1; + for (int i = 1; i < stacks; i++) + { + float angle1 = 3.14159f / 2.0f - i*3.14159f / (float)stacks; + float r = cos(angle1)*radius; + float height = sin(angle1)*radius; + + for (int j = 0; j < slices; j++) + { + float angle2 = j*2.0f*3.14159f / (float)(slices); + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(cos(angle2)*r, height, sin(angle2)*r)); + k++; + } + } + + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, -radius, 0.0f)); + + for (int i = 0; i < slices; i++) + { + mesh.add_face(vhandles[0], vhandles[1 + (1 + i) % slices], vhandles[1 + i%slices]); + + for (int j = 0; j < stacks - 2; j++) + { + int a, b, c, d; + a = 1 + j*slices + (i) % slices; + b = 1 + j*slices + (1 + i) % slices; + c = 1 + (j + 1)*slices + (1 + i) % slices; + d = 1 + (j + 1)*slices + (i) % slices; + mesh.add_face(vhandles[a], vhandles[b], vhandles[c], vhandles[d]); + } + mesh.add_face(vhandles[1 + slices*(stacks - 1)], + vhandles[1 + (stacks - 2)*slices + (i) % slices], + vhandles[1 + (stacks - 2)*slices + (1 + i) % slices]); + } +} + +//create a torus mesh +void CreateTorus(HEMesh& mesh, float r, float R, int nsides, int rings) +{ + assert(nsides >= 3 && rings >= 3); + + mesh.clear(); + + int n = rings*nsides; + std::vector vhandles(n); + int k = 0; + for (int i = 0; i < rings; i++) + { + float angle1 = (float)(i*2.0*3.14159 / (rings)); + OpenMesh::Vec3f center(cos(angle1)*R, 0.0f, sin(angle1)*R); + OpenMesh::Vec3f t1(cos(angle1), 0.0, sin(angle1)); + OpenMesh::Vec3f t2(0.0f, 1.0f, 0.0f); + + for (int j = 0; j < nsides; j++) + { + float angle2 = (float)(j*2.0*3.14159 / (nsides)); + vhandles[k] = mesh.add_vertex(center + (float)(sin(angle2)*r)*t1 + (float)(cos(angle2)*r)*t2); + k++; + } + } + + for (int i = 0; i < rings; i++) + { + for (int j = 0; j < nsides; j++) + { + int a, b, c, d; + a = (i + 1) % (rings)*(nsides)+j; + b = (i + 1) % (rings)*(nsides)+(j + 1) % (nsides); + c = i*(nsides)+(j + 1) % (nsides); + d = i*(nsides)+j; + mesh.add_face( + vhandles[a], vhandles[b], + vhandles[c], vhandles[d]); + + + + } + } +} + + +//creates an icosahedron mesh in m +// radius is the radius of the circum sphere +void CreateIcosahedron(HEMesh& mesh, float radius) +{ + mesh.clear(); + + float a = (float)(radius*4.0 / sqrt(10.0 + 2.0*sqrt(5.0))); + float h = (float)cos(2.0*asin(a / (2.0*radius)))*radius; + float r2 = (float)sqrt(radius*radius - h*h); + + std::vector vhandles(12); + int k = 0; + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f(0, radius, 0)); + + for (int i = 0; i < 5; i++) + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f((float)cos(i*72.0*3.14159 / 180.0)*r2, h, -(float)sin(i*72.0*3.14159 / 180.0)*r2)); + + for (int i = 0; i < 5; i++) + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f((float)cos(36.0*3.14159 / 180.0 + i*72.0*3.14159 / 180.0)*r2, -h, -(float)sin(36.0*3.14159 / 180.0 + i*72.0*3.14159 / 180.0)*r2)); + + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(0, -radius, 0)); + + for (int i = 0; i < 5; i++) + { + mesh.add_face(vhandles[0], vhandles[i + 1], vhandles[(i + 1) % 5 + 1]); + mesh.add_face(vhandles[11], vhandles[(i + 1) % 5 + 6], vhandles[i + 6]); + mesh.add_face(vhandles[i + 1], vhandles[i + 6], vhandles[(i + 1) % 5 + 1]); + mesh.add_face(vhandles[(i + 1) % 5 + 1], vhandles[i + 6], vhandles[(i + 1) % 5 + 6]); + } +} + + +//creates an octaeder mesh +// radius is the radius of the circum sphere +void CreateOctahedron(HEMesh& mesh, float radius) +{ + mesh.clear(); + + std::vector vhandles(6); + int k = 0; + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f(0, radius, 0)); + + + for (int i = 0; i < 4; i++) + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f((float)cos(i*3.14159 / 2.0)*radius, 0, -(float)sin(i*3.14159 / 2.0)*radius)); + + + vhandles[k++] = mesh.add_vertex(OpenMesh::Vec3f(0, -radius, 0)); + + + + for (int i = 0; i < 4; i++) + { + mesh.add_face(vhandles[0], vhandles[i + 1], vhandles[(i + 1) % 4 + 1]); + mesh.add_face(vhandles[5], vhandles[(i + 1) % 4 + 1], vhandles[i + 1]); + } +} + +//create a unit arrow +void CreateUnitArrow(HEMesh& mesh, float stem_radius, float head_radius, float stem_height, int slices, int stem_stacks) +{ + assert(slices >= 3 && stem_stacks >= 1 && stem_height <= 1 && stem_height >= 0); + + float head_height = (float)1 - stem_height; + mesh.clear(); + + int n = 2 + slices*(stem_stacks + 2); + std::vector vhandles(n); + + float height = stem_height + head_height; + vhandles[0] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, height, 0.0f)); + + int k = 1; + for (int j = 0; j < slices; j++) + { + float angle2 = j*2.0f*3.14159f / (float)(slices); + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(cos(angle2)*head_radius, stem_height, sin(angle2)*head_radius)); + k++; + } + + for (int i = 0; i < stem_stacks + 1; i++) + { + float h = (stem_stacks - i)*stem_height / (stem_stacks); + + for (int j = 0; j < slices; j++) + { + float angle2 = j*2.0f*3.14159f / (float)(slices); + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(cos(angle2)*stem_radius, h, sin(angle2)*stem_radius)); + k++; + } + } + vhandles[k] = mesh.add_vertex(OpenMesh::Vec3f(0.0f, 0.0f, 0.0f)); + + for (int i = 0; i < slices; i++) + { + mesh.add_face(vhandles[0], vhandles[1 + (1 + i) % slices], vhandles[1 + i%slices]); + + for (int j = 0; j < stem_stacks + 1; j++) + { + int a, b, c, d; + a = 1 + j*slices + (i) % slices; + b = 1 + j*slices + (1 + i) % slices; + c = 1 + (j + 1)*slices + (1 + i) % slices; + d = 1 + (j + 1)*slices + (i) % slices; + mesh.add_face(vhandles[a], vhandles[b], vhandles[c], vhandles[d]); + } + mesh.add_face(vhandles[vhandles.size() - 1], + vhandles[1 + (stem_stacks + 1)*slices + (i) % slices], + vhandles[1 + (stem_stacks + 1)*slices + (1 + i) % slices]); + } +} diff --git a/exercise2/src/ShellExtraction.cpp b/exercise2/src/ShellExtraction.cpp new file mode 100644 index 0000000..8921532 --- /dev/null +++ b/exercise2/src/ShellExtraction.cpp @@ -0,0 +1,17 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "ShellExtraction.h" + +#include + +unsigned int ExtractShells(HEMesh& m, OpenMesh::FPropHandleT perFaceShellIndex) +{ + //reset the shell indices to -1 for every face + for (auto f : m.faces()) + m.property(perFaceShellIndex, f) = -1; + + /*Task 2.2.3*/ + return 0; +} \ No newline at end of file diff --git a/exercise2/src/Smoothing.cpp b/exercise2/src/Smoothing.cpp new file mode 100644 index 0000000..92b8435 --- /dev/null +++ b/exercise2/src/Smoothing.cpp @@ -0,0 +1,29 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Smoothing.h" + +#include + +void SmoothUniformLaplacian(HEMesh& m, float lambda, unsigned int iterations) +{ + /*Task 2.2.4*/ +} + +void AddNoise(HEMesh& m) +{ + std::mt19937 rnd; + std::normal_distribution dist; + + for (auto v : m.vertices()) + { + OpenMesh::Vec3f n; + m.calc_vertex_normal_correct(v, n); //normal scales with area + float areaScale = n.norm(); + float lengthScale = sqrt(areaScale); + n = lengthScale / areaScale * n; + + m.point(v) += 0.1f * dist(rnd) * n; + } +} \ No newline at end of file diff --git a/exercise2/src/Stripification.cpp b/exercise2/src/Stripification.cpp new file mode 100644 index 0000000..4810102 --- /dev/null +++ b/exercise2/src/Stripification.cpp @@ -0,0 +1,27 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Stripification.h" + +#include +#include "sample_set.h" + + + + +unsigned int ExtractTriStrips(HEMesh& mesh, OpenMesh::FPropHandleT perFaceStripIdProperty, unsigned int nTrials) +{ + //prepare random engine + std::mt19937 eng; + + //initialize strip index to -1 for each face + for (auto f : mesh.faces()) + mesh.property(perFaceStripIdProperty, f) = -1; + + int nStrips = 0; + + /*Task 2.2.5*/ + + return nStrips; +} \ No newline at end of file diff --git a/exercise2/src/SurfaceArea.cpp b/exercise2/src/SurfaceArea.cpp new file mode 100644 index 0000000..863a66c --- /dev/null +++ b/exercise2/src/SurfaceArea.cpp @@ -0,0 +1,15 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "SurfaceArea.h" + +#include + +float ComputeSurfaceArea(const HEMesh& m) +{ + float area = 0; + /* Task 2.2.2 */ + std::cout << "Area computation is not implemented." << std::endl; + return area; +} \ No newline at end of file diff --git a/exercise2/src/Viewer.cpp b/exercise2/src/Viewer.cpp new file mode 100644 index 0000000..dd3b377 --- /dev/null +++ b/exercise2/src/Viewer.cpp @@ -0,0 +1,255 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Viewer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "Primitives.h" +#include "SurfaceArea.h" +#include "Volume.h" +#include "ShellExtraction.h" +#include "Smoothing.h" +#include "Stripification.h" + +const int segmentColorCount = 12; +const float segmentColors[segmentColorCount][3] = +{ + { 0.651f, 0.808f, 0.890f }, + { 0.122f, 0.471f, 0.706f }, + { 0.698f, 0.875f, 0.541f }, + { 0.200f, 0.627f, 0.173f }, + { 0.984f, 0.604f, 0.600f }, + { 0.890f, 0.102f, 0.110f }, + { 0.992f, 0.749f, 0.435f }, + { 1.000f, 0.498f, 0.000f }, + { 0.792f, 0.698f, 0.839f }, + { 0.416f, 0.239f, 0.604f }, + { 1.000f, 1.000f, 0.600f }, + { 0.694f, 0.349f, 0.157f }, + +}; + +Viewer::Viewer() + : AbstractViewer("CG1 Exercise 2"), + renderer(polymesh) +{ + SetupGUI(); + + polymesh.add_property(faceIdProperty); + polymesh.add_property(faceColorProperty); +} + +void Viewer::SetupGUI() +{ + auto mainWindow = SetupMainWindow(); + + auto loadFileBtn = new nanogui::Button(mainWindow, "Load Mesh"); + loadFileBtn->setCallback([this]() { + std::vector> fileTypes; + fileTypes.push_back(std::make_pair("obj", "OBJ File")); + auto file = nanogui::file_dialog(fileTypes, false); + if (!file.empty()) + { + polymesh.clear(); + if (!OpenMesh::IO::read_mesh(polymesh, file)) + { + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Load Mesh", + "The specified file could not be loaded"); + } + else + MeshUpdated(true); + } + }); + + auto primitiveBtn = new nanogui::PopupButton(mainWindow, "Create Primitive"); + primitiveBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); + + auto quadBtn = new nanogui::Button(primitiveBtn->popup(), "Quad"); + quadBtn->setCallback([this]() { CreateQuad(polymesh); MeshUpdated(true); }); + + auto diskBtn = new nanogui::Button(primitiveBtn->popup(), "Disk"); + diskBtn->setCallback([this]() { CreateDisk(polymesh, 1, 20); MeshUpdated(true); }); + + auto tetBtn = new nanogui::Button(primitiveBtn->popup(), "Tetrahedron"); + tetBtn->setCallback([this]() { CreateTetrahedron(polymesh); MeshUpdated(true); }); + + auto octaBtn = new nanogui::Button(primitiveBtn->popup(), "Octahedron"); + octaBtn->setCallback([this]() { CreateOctahedron(polymesh, 1); MeshUpdated(true); }); + + auto cubeBtn = new nanogui::Button(primitiveBtn->popup(), "Cube"); + cubeBtn->setCallback([this]() { CreateCube(polymesh); MeshUpdated(true); }); + + auto icoBtn = new nanogui::Button(primitiveBtn->popup(), "Icosahedron"); + icoBtn->setCallback([this]() { CreateIcosahedron(polymesh, 1); MeshUpdated(true); }); + + auto cylBtn = new nanogui::Button(primitiveBtn->popup(), "Cylinder"); + cylBtn->setCallback([this]() { CreateCylinder(polymesh, 0.3f, 1, 20, 10); MeshUpdated(true); }); + + auto sphereBtn = new nanogui::Button(primitiveBtn->popup(), "Sphere"); + sphereBtn->setCallback([this]() { CreateSphere(polymesh, 1, 20, 20); MeshUpdated(true); }); + + auto torusBtn = new nanogui::Button(primitiveBtn->popup(), "Torus"); + torusBtn->setCallback([this]() { CreateTorus(polymesh, 0.4f, 1, 20, 20); MeshUpdated(true); }); + + auto arrowBtn = new nanogui::Button(primitiveBtn->popup(), "Arrow"); + arrowBtn->setCallback([this]() { CreateUnitArrow(polymesh); MeshUpdated(true); }); + + auto calcAreaBtn = new nanogui::Button(mainWindow, "Calculate Mesh Area"); + calcAreaBtn->setCallback([this]() { + auto area = ComputeSurfaceArea(polymesh); + std::stringstream ss; + ss << "The mesh has an area of " << area << "."; + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Surface Area", + ss.str()); + }); + + auto calcVolBtn = new nanogui::Button(mainWindow, "Calculate Mesh Volume"); + calcVolBtn->setCallback([this]() { + //Triangulate the mesh if it is not a triangle mesh + for (auto f : polymesh.faces()) + { + if (polymesh.valence(f) > 3) + { + std::cout << "Triangulating mesh." << std::endl; + polymesh.triangulate(); + MeshUpdated(); + break; + } + } + + auto vol = ComputeVolume(polymesh); + std::stringstream ss; + ss << "The mesh has a volume of " << vol << "."; + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Volume", + ss.str()); + }); + + auto extractShellsBtn = new nanogui::Button(mainWindow, "Extract Shells"); + extractShellsBtn->setCallback([this]() { + auto count = ExtractShells(polymesh, faceIdProperty); + std::stringstream ss; + ss << "The mesh has " << count << " shells."; + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Shell Extraction", + ss.str()); + + ColorMeshFromIds(); + }); + + auto noiseBtn = new nanogui::Button(mainWindow, "Add Noise"); + noiseBtn->setCallback([this]() { AddNoise(polymesh); MeshUpdated(); }); + + nanogui::TextBox* txtSmoothingIterations; + auto sldSmoothingIterations = nse::gui::AddLabeledSlider(mainWindow, "Smoothing Iterations", std::make_pair(1, 100), 20, txtSmoothingIterations); + sldSmoothingIterations->setCallback([this, txtSmoothingIterations](float value) + { + smoothingIterations = (unsigned int)std::round(value); + txtSmoothingIterations->setValue(std::to_string(smoothingIterations)); + }); + sldSmoothingIterations->callback()(sldSmoothingIterations->value()); + + sldSmoothingStrength = nse::gui::AddLabeledSliderWithDefaultDisplay(mainWindow, "Smoothing Strength", std::make_pair(0.0f, 1.0f), 0.1f, 2); + + + auto smoothBtn = new nanogui::Button(mainWindow, "Laplacian Smoothing"); + smoothBtn->setCallback([this]() { + SmoothUniformLaplacian(polymesh, sldSmoothingStrength->value(), smoothingIterations); + MeshUpdated(); + }); + + nanogui::TextBox* txtStripificationTrials; + auto sldStripificationTrials = nse::gui::AddLabeledSlider(mainWindow, "Stripification Trials", std::make_pair(1, 50), 20, txtStripificationTrials); + sldStripificationTrials->setCallback([this, txtStripificationTrials](float value) + { + stripificationTrials = (unsigned int)std::round(value); + txtStripificationTrials->setValue(std::to_string(stripificationTrials)); + }); + sldStripificationTrials->callback()(sldStripificationTrials->value()); + + auto stripifyBtn = new nanogui::Button(mainWindow, "Extract Triangle Strips"); + stripifyBtn->setCallback([this]() { + //Triangulate the mesh if it is not a triangle mesh + for (auto f : polymesh.faces()) + { + if (polymesh.valence(f) > 3) + { + std::cout << "Triangulating mesh." << std::endl; + polymesh.triangulate(); + MeshUpdated(); + break; + } + } + + auto count = ExtractTriStrips(polymesh, faceIdProperty, stripificationTrials); + std::stringstream ss; + ss << "The mesh has " << count << " triangle strips."; + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Shell Extraction", + ss.str()); + + ColorMeshFromIds(); + }); + + shadingBtn = new nanogui::ComboBox(mainWindow, { "Smooth Shading", "Flat Shading" }); + + performLayout(); +} + +void Viewer::ColorMeshFromIds() +{ + //Set face colors + for (auto f : polymesh.faces()) + { + auto shell = polymesh.property(faceIdProperty, f); + if (shell < 0) + polymesh.property(faceColorProperty, f) = Eigen::Vector4f(0, 0, 0, 1); + else + { + auto& color = segmentColors[shell % segmentColorCount]; + polymesh.property(faceColorProperty, f) = Eigen::Vector4f(color[0], color[1], color[2], 1); + } + } + hasColors = true; + MeshUpdated(); +} + +void Viewer::MeshUpdated(bool initNewMesh) +{ + if (initNewMesh) + { + hasColors = false; + + //calculate the bounding box of the mesh + nse::math::BoundingBox bbox; + for (auto v : polymesh.vertices()) + bbox.expand(ToEigenVector(polymesh.point(v))); + camera().FocusOnBBox(bbox); + } + + if (hasColors) + renderer.UpdateWithPerFaceColor(faceColorProperty); + else + renderer.Update(); +} + +void Viewer::drawContents() +{ + glEnable(GL_DEPTH_TEST); + + Eigen::Matrix4f view, proj; + camera().ComputeCameraMatrices(view, proj); + + renderer.Render(view, proj, shadingBtn->selectedIndex() == 1); +} \ No newline at end of file diff --git a/exercise2/src/Volume.cpp b/exercise2/src/Volume.cpp new file mode 100644 index 0000000..39516c2 --- /dev/null +++ b/exercise2/src/Volume.cpp @@ -0,0 +1,15 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Volume.h" + +#include + +float ComputeVolume(const HEMesh& m) +{ + float vol = 0; + /*Task 2.2.2*/ + std::cout << "Volume calculation is not implemented." << std::endl; + return vol; +} \ No newline at end of file diff --git a/exercise2/src/main.cpp b/exercise2/src/main.cpp new file mode 100644 index 0000000..e01821b --- /dev/null +++ b/exercise2/src/main.cpp @@ -0,0 +1,36 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include + +#include + +#include "Viewer.h" + +int main(int argc, char* argv[]) +{ + nanogui::init(); + + { + nanogui::ref viewer = new Viewer(); + viewer->setVisible(true); + + nse::util::GLDebug::SetupDebugCallback(); + nse::util::GLDebug::IgnoreGLError(131185); //buffer usage info + + try + { + nanogui::mainloop(); + } + catch (std::runtime_error& e) + { + std::cerr << e.what() << std::endl; + } + + } + + nanogui::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/exercise3/CMakeLists.txt b/exercise3/CMakeLists.txt new file mode 100644 index 0000000..4d0a509 --- /dev/null +++ b/exercise3/CMakeLists.txt @@ -0,0 +1,16 @@ +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) + +add_executable(Exercise3 MACOSX_BUNDLE + src/main.cpp + src/Viewer.cpp include/Viewer.h + src/AABBTree.cpp include/AABBTree.h + src/Box.cpp include/Box.h + src/LineSegment.cpp include/LineSegment.h + src/Point.cpp include/Point.h + src/Triangle.cpp include/Triangle.h + include/GridUtils.h + src/HashGrid.cpp include/HashGrid.h + src/GridTraverser.cpp include/GridTraverser.h + ) + +target_link_libraries(Exercise3 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise3/include/AABBTree.h b/exercise3/include/AABBTree.h new file mode 100644 index 0000000..67b6868 --- /dev/null +++ b/exercise3/include/AABBTree.h @@ -0,0 +1,513 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include + +#include +#include "Box.h" +#include "Triangle.h" +#include "LineSegment.h" +#include "Point.h" + + +/** +* Axis aligned bounding volume hierachy data structure. +*/ +template +class AABBTree +{ +public: + typedef std::vector primitive_list; + //iterator type pointing inside the primitive list + typedef typename primitive_list::iterator PrimitiveIterator; + //const iterator type pointing inside the primitive list + typedef typename primitive_list::const_iterator const_primitive_iterator; + + //abstract base class defining the common interface of all aabb tree node + class AABBNode + { + protected: + //storage of bounding box assosiated with aabb_node + Box bounds; + public: + AABBNode() { + } + + AABBNode(const Box& b): bounds(b) { + } + + //returns the bounding box of the node + Box GetBounds() const + { + return bounds; + } + + virtual int NumPrimitives() const = 0; + + + //this method must be implemented to return true for a leaf node and false for a non_lef node + virtual bool IsLeaf() const = 0; + //virtual destructor + virtual ~AABBNode() {} + + }; + ///a class representing a leaf node of an aabb tree (non split node) + class AABBLeafNode: public AABBNode + { + + //internal storage to the range (begin and end pointer) of the primitives associated with the current leaf node + PrimitiveIterator primitivesBegin, primitivesEnd; + public: + + + //construct a leaf node from + AABBLeafNode(const PrimitiveIterator& primitivesBegin, + const PrimitiveIterator& primitivesEnd, + const Box& b): + primitivesBegin(primitivesBegin),primitivesEnd(primitivesEnd), AABBNode(b) + { + } + + //return always true because its a leaf node + bool IsLeaf() const + { + return true; + } + //returns the number primitives assosiated with the current leaf + int NumPrimitives() const + { + return (int)(primitivesEnd-primitivesBegin); + } + + const_primitive_iterator begin() const + { + return primitivesBegin; + } + + const_primitive_iterator end() const + { + return primitivesEnd; + } + + + }; + + ///a class representing a split node of an aabb tree (non leaf node) + class AABBSplitNode: public AABBNode + { + //child pointers + AABBNode* children[2]; + public: + //default constructor + AABBSplitNode() + { + children[0] = children[1] = nullptr; + } + //construct a split node from given Left and right child pointers and given bounding box b of the node + AABBSplitNode(AABBNode* left_child, AABBNode* right_child,const Box& b): AABBNode(b) + { + children[0] = left_child; + children[1] = right_child; + } + + //destructor of node, recursively deleted whole subtree + ~AABBSplitNode() + { + if(Left() != nullptr) + delete Left(); + if(Right() != nullptr) + delete Right(); + } + + //returns always false because its a split node + bool IsLeaf() const + { + return false; + } + + //returns a pointer to the left child node + AABBNode* Left() + { + return children[0]; + } + //returns a pointer to the right child node + AABBNode* Right() + { + return children[1]; + } + + //returns a const pointer to the left child node + const AABBNode* Left() const + { + return children[0]; + } + + //returns a const pointer to the right child node + const AABBNode* Right() const + { + return children[1]; + } + + //counts the number of primitives of the subtree + int NumPrimitives() const + { + return Left()->NumPrimitives() + Right()->NumPrimitives(); + } + + }; + +private: + //search entry used internally for nearest and k nearest primitive queries + struct SearchEntry + { + //squared distance to node from query point + float sqrDistance; + //node + const AABBNode* node; + + //constructor + SearchEntry(float sqrDistance, const AABBNode* node) + : sqrDistance(sqrDistance), node(node) + { } + + //search entry a < b means a.sqr_distance > b. sqr_distance + bool operator<(const SearchEntry& e) const + { + return sqrDistance > e.sqrDistance; + } + }; + + //result entry for nearest and k nearest primitive queries + struct ResultEntry + { + //squared distance from query point to primitive + float sqrDistance; + //pointer to primitive + const Primitive* prim; + //default constructor + ResultEntry() + : sqrDistance(std::numeric_limits::infinity()), prim(nullptr) + { } + //constructor + ResultEntry(float sqrDistance, const Primitive* p) + : sqrDistance(sqrDistance), prim(p) + { } + //result_entry are sorted by their sqr_distance using this less than operator + bool operator<(const ResultEntry& e) const + { + return sqrDistance < e.sqrDistance; + } + }; + + //list of all primitives in the tree + primitive_list primitives; + //maximum allowed tree depth to stop tree construction + int maxDepth; + //minimal number of primitives to stop tree construction + int minSize; + //pointer to the root node of the tree + AABBNode *root; + //a flag indicating if the tree is constructed + bool completed; + + +public: + //returns a pointer to the root node of the tree + AABBNode* Root() + { + assert(IsCompleted()); + return root; + } + + //returns a const pointer to the root node of the tree + const AABBNode* Root() const + { + assert(IsCompleted()); + return root; + } + + //constructor of aabb tree + //default maximal tree depth is 20 + //default minimal size of a node not to be further subdivided in the cnstruction process is two + AABBTree(int maxDepth=20, int minSize=2): + maxDepth(maxDepth),minSize(minSize),root(nullptr),completed(false) + { + + } + + //copy constructor + AABBTree(const AABBTree& other) + { + primitives = other.primitives; + maxDepth = other.maxDepth; + minSize = other.minSize; + root = CopyTree(other.primitives,other.root); + completed = other.completed; + } + + //move constructor + AABBTree(AABBTree&& other):root(nullptr),completed(false) + { + *this = std::move(other); + } + + //copy assignment operator + AABBTree& operator=(const AABBTree& other) + { + if(this != &other) + { + if(root != nullptr) + delete root; + primitives = other.primitives; + maxDepth = other.maxDepth; + minSize = other.minSize; + root = CopyTree(other.primitives,other.root); + completed = other.completed; + } + return *this; + } + + //move assign operator + AABBTree& operator=(AABBTree&& other) + { + if(this != &other) + { + std::swap(primitives,other.primitives); + std::swap(maxDepth, other.maxDepth); + std::swap(minSize, other.minSize); + std::swap(root,other.root) ; + std::swap(completed, other.completed); + } + return *this; + } + + //remove all primitives from tree + void Clear() + { + primitives.clear(); + if(root != nullptr) + { + delete root; + root = nullptr; + } + completed = false; + } + + //returns true if tree is empty + bool Empty() const + { + return primitives.Empty(); + } + + //insert a primitive into internal primitive list + //this method do not construct the tree! + //call the method Complete, after insertion of all primitives + void Insert(const Primitive& p) + { + primitives.push_back(p); + completed=false; + } + + //construct the tree from all prior inserted primitives + void Complete() + { + //if tree already constructed -> delete tree + if(root != nullptr) + delete root; + //compute bounding box over all primitives using helper function + Box bounds = ComputeBounds(primitives.begin(),primitives.end()); + //initial call to the recursive tree construction method over the whole range of primitives + root = Build(primitives.begin(),primitives.end(),bounds,0); + //set completed flag to true + completed=true; + } + + //returns true if the tree can be used for queries + //if the tree is not completed call the method complete() + bool IsCompleted() const + { + return completed; + } + + //closest primitive computation via linear search + ResultEntry ClosestPrimitiveLinearSearch(const Eigen::Vector3f& q) const + { + ResultEntry best; + + auto pend = primitives.end(); + for(auto pit = primitives.begin(); pit != pend; ++pit) + { + float dist = pit->SqrDistance(q); + if(dist < best.sqrDistance) + { + best.sqrDistance = dist; + best.prim = &(*pit); + } + } + return best; + } + + //computes the k nearest neighbor primitives via linear search + std::vector ClosestKPrimitivesLinearSearch(size_t k, const Eigen::Vector3f& q) const + { + std::priority_queue k_best; + + Primitive best_p; + auto pend = primitives.end(); + for(auto pit = primitives.begin(); pit != pend; ++pit) + { + float dist = pit->SqrDistance(q); + if(k_best.size() < k ) + { + k_best.push(ResultEntry(dist,*pit)); + return; + } + if(k_best.top().SqrDistance > dist) + { + k_best.pop(); + k_best.push(ResultEntry(dist,*pit)); + } + } + std::vector result(k_best.size()); + auto rend = result.end(); + for(auto rit = result.begin(); rit != rend; ++rit) + { + *rit = k_best.top(); + k_best.pop(); + } + return result; + + } + + //closest k primitive computation + std::vector ClosestKPrimitives(size_t k,const Eigen::Vector3f& q) const + { + //student begin + return ClosestKPrimitivesLinearSearch(k,q); + //student end + } + + //returns the closest primitive and its squared distance to the point q + ResultEntry ClosestPrimitive(const Eigen::Vector3f& q) const + { + assert(IsCompleted()); + if(root == nullptr) + return ResultEntry(); + /* Task 3.2.1 */ + return ClosestPrimitiveLinearSearch(q); + } + + //return the closest point position on the closest primitive in the tree with respect to the query point q + Eigen::Vector3f ClosestPoint(const Eigen::Vector3f& p) const + { + ResultEntry r = ClosestPrimitive(p); + return r.prim->ClosestPoint(p); + } + + //return the squared distance between point p and the nearest primitive in the tree + float SqrDistance(const Eigen::Vector3f& p) const + { + ResultEntry r = ClosestPrimitive(p); + return r.SqrDistance; + } + + //return the euclidean distance between point p and the nearest primitive in the tree + float Distance(const Eigen::Vector3f& p) const + { + return sqrt(SqrDistance(p)); + } + + +protected: + + //helper function to copy a subtree + AABBNode* CopyTree(const primitive_list& other_primitives,AABBNode* node) + { + if(node == nullptr) + return nullptr; + if(node->IsLeaf()) + { + AABBLeafNode* leaf = (AABBLeafNode*)node; + return new AABBLeafNode(primitives.begin()+(leaf->primitives.begin()-other_primitives.begin()), + primitives.begin()+(leaf->primitives.end()-other_primitives.begin())); + } + else + { + AABBSplitNode* split = (AABBSplitNode*)node; + return new AABBSplitNode(CopyTree(other_primitives,split->Left()), + CopyTree(other_primitives,split->Right())); + } + } + + //helper function to compute an axis aligned bounding box over the range of primitives [begin,end) + Box ComputeBounds(const_primitive_iterator begin, + const_primitive_iterator end) + { + Box bounds; + for(auto pit = begin; pit != end; ++pit) + bounds.Insert(pit->ComputeBounds()); + return bounds; + } + + + + //recursive tree construction initially called from method complete() + //build an aabb (sub)-tree over the range of primitives [begin,end), + //the current bounding box is given by bounds and the current tree depth is given by the parameter depth + //if depth >= max_depth or the number of primitives (end-begin) <= min_size a leaf node is constructed + //otherwise split node is created + // to create a split node the range of primitives [begin,end) must be splitted and reordered into two + //sub ranges [begin,mid) and [mid,end), + //therefore sort the range of primitives [begin,end) along the largest bounding box extent by its reference + //point returned by the method ReferencePoint() + //then choose the median element as mid + // the STL routine std::nth_element would be very useful here , you only have to provide a ordering predicate + //compute the boundg boxed of the two resulting sub ranges and recursivly call build on the two subranges + //the resulting subtree are used as children of the resulting split node. + AABBNode* Build(PrimitiveIterator begin, PrimitiveIterator end, Box& bounds, int depth) + { + + if(depth >= maxDepth || end-begin <= minSize) + { + return new AABBLeafNode(begin,end,bounds); + } + + Eigen::Vector3f e = bounds.Extents(); + + int axis = 0; + float max_extent = e[0]; + if(max_extent < e[1]) + { + axis = 1; + max_extent = e[1]; + } + if(max_extent < e[2]) + { + axis = 2; + max_extent = e[2]; + } + + + PrimitiveIterator mid= begin + (end-begin)/2; + std::nth_element(begin,mid,end,[&axis](const Primitive& a, const Primitive& b) + { return a.ReferencePoint()[axis] < b.ReferencePoint()[axis];}); + + Box lbounds = ComputeBounds(begin,mid); + Box rbounds = ComputeBounds(mid,end); + + return new AABBSplitNode(Build(begin,mid,lbounds,depth+1),Build(mid,end,rbounds,depth+1),bounds); + + } +}; + +//helper function to construct an aabb tree data structure from the triangle faces of the halfedge mesh m +void BuildAABBTreeFromTriangles(const HEMesh& m, AABBTree& tree); +//helper function to construct an aabb tree data structure from the vertices of the halfedge mesh m +void BuildAABBTreeFromVertices(const HEMesh& m, AABBTree& tree); +//helper function to construct an aabb tree data structure from the edges of the halfedge mesh m +void BuildAABBTreeFromEdges(const HEMesh& m, AABBTree& tree); + diff --git a/exercise3/include/Box.h b/exercise3/include/Box.h new file mode 100644 index 0000000..114472e --- /dev/null +++ b/exercise3/include/Box.h @@ -0,0 +1,81 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include + +class Box +{ + //internal storage for lower and upper corner points of the box + std::pair bounds; + +public: + + //creates an empty box like the method Clear + Box(); + + //construct a box with the gven lower and upper corner points + Box(const Eigen::Vector3f& lbound, const Eigen::Vector3f& ubound); + + //returns the corner point which is the lower bound of the box in all dimensions + Eigen::Vector3f& LowerBound(); + + //returns the corner point which is the lower bound of the box in all dimensions + const Eigen::Vector3f& LowerBound() const; + + //returns the corner point which is the upper bound of the box in all dimensions + Eigen::Vector3f& UpperBound(); + + //returns the corner point which is the upper bound of the box in all dimensions + const Eigen::Vector3f& UpperBound() const; + + //returns a vector containing the extents of the box in all dimensions + Eigen::Vector3f Extents() const; + + //returns a vector containing the extents of the box in all dimensions divided by 2 + Eigen::Vector3f HalfExtents() const; + + //returns the center of the box + Eigen::Vector3f Center() const; + + //returns the surface area of the box + float SurfaceArea() const; + + //returns the volume of the box + float Volume() const; + + //returns the box radius for a given direction vector a + //if the box is centered at the origin + //then the projection of the box on direction a is contained within the Interval [-r,r] + float Radius(const Eigen::Vector3f& a) const; + + //returns true if the box b overlaps with the current one + bool Overlaps(const Box& b) const; + + //returns true if the point p is inside this box + bool IsInside(const Eigen::Vector3f& p) const; + + //returns true if box b is inside this box + bool IsInside(const Box& b) const; + + //creates a box which goes from [+infinity,- infinity] in al dimensions + void Clear(); + + //enlarges the box such that the point p is inside afterwards + void Insert(const Eigen::Vector3f& p); + + //enlarges the box such that box b is inside afterwards + void Insert(const Box& b); + + //returns the point on or inside the box with the smallest distance to p + Eigen::Vector3f ClosestPoint(const Eigen::Vector3f& p) const; + + //returns the squared distance between p and the box + float SqrDistance(const Eigen::Vector3f& p) const; + + //returns the euclidean distance between p and the box + float Distance(const Eigen::Vector3f& p) const; + +}; diff --git a/exercise3/include/GridTraverser.h b/exercise3/include/GridTraverser.h new file mode 100644 index 0000000..0f41bc9 --- /dev/null +++ b/exercise3/include/GridTraverser.h @@ -0,0 +1,51 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include + +class GridTraverser +{ + //ray origin and direction + Eigen::Vector3f orig,dir; + //grid cell extents + Eigen::Vector3f cellExtents; + //current cell index + Eigen::Vector3i current; + + /* you can additional attributes for incremental calculation here */ + +public: + //default constructor + GridTraverser(); + + //constructs a grid traverser for a given ray with origin o, and ray direction d for a grid with cell extents ce + GridTraverser(const Eigen::Vector3f& o, const Eigen::Vector3f&d, const Eigen::Vector3f ce); + + //accessor of ray origin + Eigen::Vector3f& Origin(); + + //const accessor of ray origin + const Eigen::Vector3f& Origin() const; + + //accessor of ray direction + Eigen::Vector3f& Direction(); + + //const accessor of ray direction + const Eigen::Vector3f& Direction() const; + + //set cell extents + void SetCellExtents(const Eigen::Vector3f& cellExtent); + + //init at origin cell + void Init(); + + //step to next cell along ray direction + void operator++(int); + + //return current cell index + Eigen::Vector3i operator*(); +}; diff --git a/exercise3/include/GridUtils.h b/exercise3/include/GridUtils.h new file mode 100644 index 0000000..5b5b5ac --- /dev/null +++ b/exercise3/include/GridUtils.h @@ -0,0 +1,34 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include + +//converts 3d floating point position pos into 3d integer grid cell index +inline Eigen::Vector3i PositionToCellIndex(const Eigen::Vector3f& pos, const Eigen::Vector3f& cellExtents) +{ + Eigen::Vector3i idx; + for(int d = 0; d < 3; ++d) + { + idx[d] = int(pos[d]/cellExtents[d]); + if (pos[d] < 0) + --idx[d]; + } + return idx; +} + +//returns true if the two Interval [lb1,ub2] and [lb2,ub2] overlap +inline bool OverlapIntervals(float lb1, float ub1, float lb2, float ub2) +{ + if(lb1 > ub1) + std::swap(lb1,ub1); + if(lb2 > ub2) + std::swap(lb2,ub2); + if(ub1 < lb2|| lb1 >ub2) + return false; + return true; +} + diff --git a/exercise3/include/HashGrid.h b/exercise3/include/HashGrid.h new file mode 100644 index 0000000..431485c --- /dev/null +++ b/exercise3/include/HashGrid.h @@ -0,0 +1,264 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include +#include +#include "Box.h" +#include "GridUtils.h" +#include "Triangle.h" +#include "Point.h" +#include "LineSegment.h" + +template +class HashGrid +{ +public: + + //hash function + struct GridHashFunc + { + size_t operator()(const Eigen::Vector3i &idx ) const + { + static const int p1 = 131071; + static const int p2 = 524287; + static const int p3 = 8191; + return idx[0] * p1 + idx[1] * p2 + idx[2] * p3; + } + }; + //type of internal hash map + typedef std::unordered_map,GridHashFunc> CellHashMapType; + +private: + //internal hash map storing the data of each non empty grid cell + //it is a map with a 3 dimensional cell index index as a key and a std::vector as value type + CellHashMapType cellHashMap; + //internal extents of a cell + Eigen::Vector3f cellExtents; + +public: + //constructor for hash grid with uniform cell extent + //initial size is used to preallocate memory for the internal unordered map + HashGrid(const float cellExtent=0.01,const int initialSize=1): cellHashMap(initialSize) + { + cellExtents[0] =cellExtents[1] =cellExtents[2] = cellExtent; + } + + //constructor for hash grid with non uniform cell extents + //initial size is used to preallocate memory for the internal unordered map + HashGrid(const Eigen::Vector3f& cellExtents,const int initialSize): cellHashMap(initialSize),cellExtents(cellExtents) + { + } + + //resize hash map with at least count buckets + void ReHash(const int count) + { + cellHashMap.rehash(count); + } + + + //converts a position to a grid index + Eigen::Vector3i PositionToIndex(const Eigen::Vector3f& pos) const + { + return PositionToCellIndex(pos, cellExtents) ; + } + + //return the center position of a cell specified by its cell key + Eigen::Vector3f CellCenter(const Eigen::Vector3i& idx) const + { + Eigen::Vector3f p; + for(int d = 0; d < 3; ++d) + p[d] = (idx[d] + 0.5f)*cellExtents[d]; + + return p; + } + + //return the center position of a cell containing give position pos + Eigen::Vector3f CellCenter(const Eigen::Vector3f& pos) const + { + return CellCenter(PositionToIndex(pos)); + } + + //return the min corner position of a cell specified by its cell key + Eigen::Vector3f CellMinPosition(const Eigen::Vector3i& key) const + { + Eigen::Vector3f p; + for(int d = 0; d < 3; ++d) + p[d] = key[d]*cellExtents[d]; + + return p; + } + + //return the min corner position of a cell containing the point pos + Eigen::Vector3f CellMinPosition(const Eigen::Vector3f& pos) const + { + return CellMinPosition(PositionToIndex(pos)); + } + + //return the max corner position of a cell specified by its cell key + Eigen::Vector3f CellMaxPosition(const Eigen::Vector3i& idx) const + { + Eigen::Vector3f p; + for(int d = 0; d < 3; ++d) + p[d] = (idx[d]+1)*cellExtents[d]; + + return p; + } + + //return the max corner position of a cell containing the point pos + Eigen::Vector3f CellMaxPosition(const Eigen::Vector3f& pos) const + { + return CellMaxPosition(PositionToIndex(pos)); + } + + //returns bounding box of cell with index idx + Box CellBounds(const Eigen::Vector3i& idx) const + { + return Box(CellMinPosition(idx),CellMaxPosition(idx)); + } + + //returns the bounding box of cell containing the point pos + Box CellBounds(const Eigen::Vector3f& pos) const + { + Eigen::Vector3i idx = PositionToIndex(pos); + return Box(CellMinPosition(idx),CellMaxPosition(idx)); + } + + //returns the extents of a grid cell + Eigen::Vector3f CellExtents() const + { + return cellExtents; + } + + //returns volume of a grid cell + float CellVolume() const + { + float vol = 0; + for(int d = 0; d < 3; ++d) + vol *= cellExtents[d]; + return vol; + } + + //removes all non empty cells from the hash grid + bool Empty(const Eigen::Vector3i& idx) const + { + auto it = cellHashMap.find(idx); + if(it == cellHashMap.end()) + return true; + return false; + } + + + //inserts primitive p into all overlapping hash grid cells + //the primitive must implement a method "box compute_bounds()" which returns an axis aligned bounding box + //and a method "bool overlaps(const box& b)" which returns true if the primitive overlaps the given box b + void Insert(const Primitive& p) + { + Box b = p.ComputeBounds(); + Eigen::Vector3f lb = b.LowerBound(); + Eigen::Vector3f ub = b.UpperBound(); + if(lb[0] > ub[0]) + return; + if(lb[1] > ub[1]) + return; + if(lb[2] > ub[2]) + return; + Eigen::Vector3i lb_idx = PositionToIndex(lb); + Eigen::Vector3i ub_idx = PositionToIndex(ub); + + + + Eigen::Vector3i idx; + for(idx[0] = lb_idx[0]; idx[0] <=ub_idx[0]; ++idx[0]) + for(idx[1] = lb_idx[1]; idx[1] <=ub_idx[1]; ++idx[1]) + for(idx[2] = lb_idx[2]; idx[2] <=ub_idx[2]; ++idx[2]) + if(p.Overlaps(CellBounds(idx))) + cellHashMap[idx].push_back(p); + + } + + + //remove all cells from hash grid + void Clear() + { + cellHashMap.clear(); + } + + //returns true if hashgrid contains no cells + bool Empty() const + { + return cellHashMap.empty(); + } + + //returns the number of non empty cells + size_t NumCells() const + { + return cellHashMap.size(); + } + + //iterator pointing to the first cell within the hashgrid + typename CellHashMapType::iterator NonEmptyCellsBegin() + { + return cellHashMap.begin(); + } + + //iterator pointing behind the last cell within the hashgrid + typename CellHashMapType::iterator NonEmptyCellsEnd() + { + return cellHashMap.end(); + } + + //const iterator pointing to the first cell within the hashgrid + typename CellHashMapType::const_iterator NonEmptyCellsBegin() const + { + return cellHashMap.begin(); + } + + //const iterator pointing behind the last cell within the hashgrid + typename CellHashMapType::const_iterator NonEmptyCellsEnd() const + { + return cellHashMap.end(); + } + + //iterator pointing to the first primitive stored in the cell idx + typename std::vector::iterator PrimitivesBegin(const Eigen::Vector3i& idx) + { + assert(!Empty(idx)); + return cellHashMap[idx].begin(); + } + + //iterator pointing after the last primitive stored in the cell idx + typename std::vector::iterator PrimitivesEnd(const Eigen::Vector3i& idx) + { + assert(!Empty(idx)); + return cellHashMap[idx].end(); + } + + //const iterator pointing to the first primitive stored in the cell idx + typename std::vector::const_iterator PrimitivesBegin(const Eigen::Vector3i& idx) const + { + assert(!Empty(idx)); + return cellHashMap[idx].cbegin(); + } + + //const iterator pointing after the last primitive stored in the cell idx + typename std::vector::const_iterator PrimitivesEnd(const Eigen::Vector3i& idx) const + { + assert(!Empty(idx)); + return cellHashMap[idx].cend(); + } +}; + +//helper function to construct a hashgrid data structure from the triangle faces of the halfedge mesh m +void BuildHashGridFromTriangles(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize); +//helper function to construct a hashgrid data structure from the vertices of the halfedge mesh m +void BuildHashGridFromVertices(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize); +//helper function to construct a hashgrid data structure from the edges of the halfedge mesh m +void BuildHashGridFromEdges(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize); + + + + diff --git a/exercise3/include/LineSegment.h b/exercise3/include/LineSegment.h new file mode 100644 index 0000000..9130023 --- /dev/null +++ b/exercise3/include/LineSegment.h @@ -0,0 +1,52 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include "Box.h" +#include "GridUtils.h" +#include + +/* +a 3d line segment primitive which can be used with the AABBTree and the HashGrid data structure +*/ +class LineSegment +{ + //internal storage of start point of line segment + Eigen::Vector3f v0; + //internal storage of end point of line segment + Eigen::Vector3f v1; + //internal storage for an edge handle + //this edge handle can be used to optionally identify the edge in a halfedge mesh data structure instance + OpenMesh::EdgeHandle h; + +public: + //default constructor + LineSegment(); + + //constructs a line segment by the two end points v0, v1 without using the edge handle + LineSegment(const Eigen::Vector3f& v0, const Eigen::Vector3f& v1); + + //construct a line segment from the edge e of the halfedge mesh m + LineSegment(const HEMesh& m,const OpenMesh::EdgeHandle& e); + + //returns an axis aligned bounding box of the line segment + Box ComputeBounds() const; + + //returns true if the line segment overlaps the given box b + bool Overlaps(const Box& b) const; + + //returns the point with smallest distance topoint p which lies on the line segment + Eigen::Vector3f ClosestPoint(const Eigen::Vector3f& p) const; + + //returns the squared distance between point p and the line segment + float SqrDistance(const Eigen::Vector3f& p) const; + + //returns the euclidean distance between point p and the line segment + float Distance(const Eigen::Vector3f& p) const; + + //returns a reference point which is on the line segment and is used to sort the primitive in the AABB tree construction + Eigen::Vector3f ReferencePoint() const; + +}; + diff --git a/exercise3/include/Point.h b/exercise3/include/Point.h new file mode 100644 index 0000000..174f416 --- /dev/null +++ b/exercise3/include/Point.h @@ -0,0 +1,52 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include +#include "Box.h" + + +class Point +{ + //internal storage of point position + Eigen::Vector3f v0; + //internal storage for a vertex handle + //this vertex handle can be used to optionally identify the vertex in a halfedge mesh data structure instance + OpenMesh::VertexHandle h; + + +public: + + //default constructor + Point(); + + //construct a point with given point position v0 + Point(const Eigen::Vector3f& v0); + + //construct a point from vertex v of giben halfedge mesh m + Point(const HEMesh &m, const OpenMesh::VertexHandle& v); + + //returns axis aligned bounding box of point + Box ComputeBounds() const; + + //returns true if point overlap with box b + bool Overlaps(const Box& b) const; + + //returns the point position + Eigen::Vector3f ClosestPoint(const Eigen::Vector3f& p) const; + + //returns the squared distance between the query point p and the current point + float SqrDistance(const Eigen::Vector3f& p) const; + + //returns the euclidean distance between the query point p and the current point + float Distance(const Eigen::Vector3f& p) const; + + //returns a the position of the point as a reference point which is used to sort the primitive in the AABB tree construction + Eigen::Vector3f ReferencePoint() const; +}; + + + + + diff --git a/exercise3/include/Triangle.h b/exercise3/include/Triangle.h new file mode 100644 index 0000000..078f7c6 --- /dev/null +++ b/exercise3/include/Triangle.h @@ -0,0 +1,48 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include "Box.h" +#include "util/OpenMeshUtils.h" + + +/* +a triangle primitive which can be used with the AABBTree and the HashGrid data structure +*/ +class Triangle +{ + //internal storage of the first vertex position of the triangle + Eigen::Vector3f v0; + //internal storage of the second vertex position of the triangle + Eigen::Vector3f v1; + //internal storage of the third vertex position of the triangle + Eigen::Vector3f v2; + //internal storage of the optional face handle to identify the originating face in a half edge mesh instance + OpenMesh::FaceHandle h; + +public: + + //default constructor + Triangle(); + //constructs a triangle using the vertex positions v0,v1 and v2 + Triangle(const Eigen::Vector3f& v0, const Eigen::Vector3f& v1,const Eigen::Vector3f& v2); + //constructs a triangle from the face f of the given halfedge mesh m + Triangle(const HEMesh&m, const OpenMesh::FaceHandle& f); + //returns the axis aligned bounding box of the triangle + Box ComputeBounds() const; + //returns true if the triangle overlaps the given box b + bool Overlaps(const Box& b) const; + //returns the barycentric coordinates of the point with thesmallest distance to point p which lies on the triangle + void ClosestPointBarycentric(const Eigen::Vector3f& p, float& l0, float& l1, float& l2) const; + //returns the point with smallest distance to point p which lies on the triangle + Eigen::Vector3f ClosestPoint(const Eigen::Vector3f& p) const; + //returns the squared distance between point p and the triangle + float SqrDistance(const Eigen::Vector3f& p) const; + //returns the euclidean distance between point p and the triangle + float Distance(const Eigen::Vector3f& p) const; + //returns a reference point which is on the triangle and is used to sort the primitive in the AABB tree construction + Eigen::Vector3f ReferencePoint() const; + +}; + diff --git a/exercise3/include/Viewer.h b/exercise3/include/Viewer.h new file mode 100644 index 0000000..fcda12f --- /dev/null +++ b/exercise3/include/Viewer.h @@ -0,0 +1,90 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include +#include + +#include "AABBTree.h" +#include "HashGrid.h" +#include "Point.h" +#include "LineSegment.h" +#include "Triangle.h" + +#include + +class Viewer : public nse::gui::AbstractViewer +{ +public: + Viewer(); + + void drawContents(); + +private: + + enum PrimitiveType + { + Vertex, Edge, Tri + }; + + void SetupGUI(); + void MeshUpdated(); + + void FindClosestPoint(const Eigen::Vector3f& p); + void BuildGridVBO(); + void BuildRayVBOs(); + + template + void BuildGridVBO(const Grid& grid) + { + std::vector positions; + for (auto it = grid.NonEmptyCellsBegin(); it != grid.NonEmptyCellsEnd(); ++it) + { + auto box = grid.CellBounds(it->first); + AddBoxVertices(box, positions); + } + + ShaderPool::Instance()->simpleShader.bind(); + gridVAO.bind(); + gridPositions.uploadData(positions).bindToAttribute("position"); + gridVAO.unbind(); + gridIndices = (GLuint)positions.size(); + } + + void AddBoxVertices(const Box& box, std::vector& positions); + + nanogui::ComboBox* shadingBtn; + nanogui::CheckBox* chkRenderMesh; + nanogui::CheckBox* chkRenderGrid; + nanogui::CheckBox* chkRenderRay; + int raySteps; + + nse::gui::VectorInput* sldQuery, *sldRayOrigin, *sldRayDir; + nanogui::ComboBox* cmbPrimitiveType; + + HEMesh polymesh; + float bboxMaxLength; + MeshRenderer renderer; + + AABBTree vertexTree; + AABBTree edgeTree; + AABBTree triangleTree; + + HashGrid vertexGrid; + HashGrid edgeGrid; + HashGrid triangleGrid; + + nse::gui::GLBuffer closestPositions; + nse::gui::GLVertexArray closestVAO; + + nse::gui::GLBuffer gridPositions; + nse::gui::GLVertexArray gridVAO; + GLuint gridIndices; + + nse::gui::GLBuffer rayPositions, rayCellsPositions; + nse::gui::GLVertexArray rayVAO, rayCellsVAO; + GLuint rayCellsIndices; +}; diff --git a/exercise3/src/AABBTree.cpp b/exercise3/src/AABBTree.cpp new file mode 100644 index 0000000..dd09c4f --- /dev/null +++ b/exercise3/src/AABBTree.cpp @@ -0,0 +1,42 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "AABBTree.h" +#include + +void BuildAABBTreeFromTriangles(const HEMesh& m, AABBTree& tree) +{ + std::cout << "Building AABB tree from triangles .." << std::endl; + tree.Clear(); + auto fend = m.faces_end(); + for(auto fit = m.faces_begin(); fit != fend; ++fit) + tree.Insert(Triangle(m,*fit)); + + tree.Complete(); + std::cout << "Done." << std::endl; +} + +void BuildAABBTreeFromVertices(const HEMesh& m, AABBTree& tree) +{ + std::cout << "Building AABB tree from vertices .." << std::endl; + tree.Clear(); + auto vend = m.vertices_end(); + for(auto vit = m.vertices_begin(); vit != vend; ++vit) + tree.Insert(Point(m,*vit)); + + tree.Complete(); + std::cout << "Done." << std::endl; +} + +void BuildAABBTreeFromEdges(const HEMesh& m, AABBTree& tree) +{ + std::cout << "Building AABB tree from edges .." << std::endl; + tree.Clear(); + auto eend = m.edges_end(); + for(auto eit = m.edges_begin(); eit != eend; ++eit) + tree.Insert(LineSegment(m,*eit)); + + tree.Complete(); + std::cout << "Done." << std::endl; +} diff --git a/exercise3/src/Box.cpp b/exercise3/src/Box.cpp new file mode 100644 index 0000000..0248a8e --- /dev/null +++ b/exercise3/src/Box.cpp @@ -0,0 +1,178 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Box.h" +#include "GridUtils.h" +#include + + +//creates an empty box like the method Clear +Box::Box() +{ + Clear(); +} + +//construct a box with the gven lower and upper corner points +Box::Box(const Eigen::Vector3f& lbound, const Eigen::Vector3f& ubound) +{ + bounds = std::make_pair(lbound,ubound); +} + +//returns the corner point which is the lower bound of the box in all dimensions +Eigen::Vector3f& Box::LowerBound() +{ + return bounds.first; +} + +//returns the corner point which is the lower bound of the box in all dimensions +const Eigen::Vector3f& Box::LowerBound() const +{ + return bounds.first; +} + +//returns the corner point which is the upper bound of the box in all dimensions +Eigen::Vector3f& Box::UpperBound() +{ + return bounds.second; +} + +//returns the corner point which is the upper bound of the box in all dimensions +const Eigen::Vector3f& Box::UpperBound() const +{ + return bounds.second; +} + +//returns a vector containing the extents of the box in all dimensions +Eigen::Vector3f Box::Extents() const +{ + return UpperBound()-LowerBound(); +} + +//returns a vector containing the extents of the box in all dimensions divided by 2 +Eigen::Vector3f Box::HalfExtents() const +{ + return Extents()/2.0f; +} +//returns the center of the box +Eigen::Vector3f Box::Center() const +{ + return (LowerBound()+UpperBound())/2.0f; +} + +//returns the surface area of the box +float Box::SurfaceArea() const +{ + Eigen::Vector3f e = Extents(); + return 2.0f*(e[0]*e[1] + e[1]*e[2] + e[0]*e[2]); +} + +//returns the volume of the box +float Box::Volume() const +{ + Eigen::Vector3f e = Extents(); + return e[0] + e[1]+ e[2]; +} + +//returns the box radius for a given direction vector a +//if the box is centered at the origin +//then the projection of the box on direction a is contained within the Interval [-r,r] +float Box::Radius(const Eigen::Vector3f& a) const +{ + Eigen::Vector3f h = HalfExtents(); + return h[0]*std::abs(a[0]) + h[1]*std::abs(a[1]) + h[2]*std::abs(a[2]); +} + +//returns true if the box b overlaps with the current one +bool Box:: Overlaps(const Box& b) const +{ + Eigen::Vector3f lb1 = LowerBound(); + Eigen::Vector3f ub1 = UpperBound(); + + Eigen::Vector3f lb2 = b.LowerBound(); + Eigen::Vector3f ub2 = b.UpperBound(); + + if(!OverlapIntervals(lb1[0], ub1[0],lb2[0], ub2[0])) + return false; + if(!OverlapIntervals(lb1[1], ub1[1],lb2[1], ub2[1])) + return false; + if(!OverlapIntervals(lb1[2], ub1[2],lb2[2], ub2[2])) + return false; + + return true; +} + +//returns true if the point p is inside this box +bool Box::IsInside(const Eigen::Vector3f& p) const +{ + const Eigen::Vector3f& lbound = LowerBound(); + const Eigen::Vector3f& ubound = UpperBound(); + if(p[0] < lbound[0] || p[0] > ubound[0] || + p[1] < lbound[1] || p[1] > ubound[1] || + p[2] < lbound[2] || p[2] > ubound[2]) + return false; + return true; +} + +//returns true if box b is inside this box +bool Box::IsInside(const Box& b) const +{ + return IsInside(b.LowerBound()) && IsInside(b.UpperBound()); +} + +//creates a box which goes from [+infinity,- infinity] in al dimensions +void Box::Clear() +{ + const float infty = std::numeric_limits::infinity(); + const Eigen::Vector3f vec_infty = Eigen::Vector3f(infty,infty,infty); + bounds = std::make_pair(vec_infty,-vec_infty); +} + +//enlarges the box such that the point p is inside afterwards +void Box::Insert(const Eigen::Vector3f& p) +{ + Eigen::Vector3f& lbound = LowerBound(); + Eigen::Vector3f& ubound = UpperBound(); + lbound[0] = (std::min)(p[0],lbound[0]); + lbound[1] = (std::min)(p[1],lbound[1]); + lbound[2] = (std::min)(p[2],lbound[2]); + ubound[0] = (std::max)(p[0],ubound[0]); + ubound[1] = (std::max)(p[1],ubound[1]); + ubound[2] = (std::max)(p[2],ubound[2]); +} + +//enlarges the box such that box b is inside afterwards +void Box::Insert(const Box& b) +{ + Insert(b.LowerBound()); + Insert(b.UpperBound()); +} + +//returns the point on or inside the box with the smallest distance to p +Eigen::Vector3f Box::ClosestPoint(const Eigen::Vector3f& p) const +{ + Eigen::Vector3f q; + const Eigen::Vector3f& lbound = LowerBound(); + const Eigen::Vector3f& ubound = UpperBound(); + + q[0] = std::min(std::max(p[0],lbound[0]),ubound[0]); + q[1] = std::min(std::max(p[1],lbound[1]),ubound[1]); + q[2] = std::min(std::max(p[2],lbound[2]),ubound[2]); + + return q; +} + +//returns the squared distance between p and the box +float Box::SqrDistance(const Eigen::Vector3f& p) const +{ + Eigen::Vector3f d = p-ClosestPoint(p); + return d.dot(d); +} + +//returns the euclidean distance between p and the box +float Box::Distance(const Eigen::Vector3f& p) const +{ + return sqrt(SqrDistance(p)); +} + + diff --git a/exercise3/src/GridTraverser.cpp b/exercise3/src/GridTraverser.cpp new file mode 100644 index 0000000..8173193 --- /dev/null +++ b/exercise3/src/GridTraverser.cpp @@ -0,0 +1,63 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "GridTraverser.h" +#include "GridUtils.h" + + +GridTraverser::GridTraverser() +{ } + +GridTraverser::GridTraverser(const Eigen::Vector3f& o, const Eigen::Vector3f&d, const Eigen::Vector3f cell_extents) + : orig(o), dir(d), cellExtents(cell_extents) +{ + dir.normalize(); + Init(); +} + +Eigen::Vector3f& GridTraverser::Origin() +{ + return orig; +} +const Eigen::Vector3f& GridTraverser::Origin() const +{ + return orig; +} + +Eigen::Vector3f& GridTraverser::Direction() +{ + return dir; +} + +const Eigen::Vector3f& GridTraverser::Direction() const +{ + return dir; +} + +void GridTraverser::SetCellExtents(const Eigen::Vector3f& cellExtent) +{ + this->cellExtents = cellExtent; + Init(); +} + +void GridTraverser::Init() +{ + current = PositionToCellIndex(orig, cellExtents); + /* Task 3.2.2 */ + //you can add some precalculation code here +} + +void GridTraverser::operator++(int) +{ + /* Task 3.2.2 */ + //traverse one step along the ray + //update the cell index stored in attribute "current" +} + +Eigen::Vector3i GridTraverser::operator*() +{ + return current; +} + + diff --git a/exercise3/src/HashGrid.cpp b/exercise3/src/HashGrid.cpp new file mode 100644 index 0000000..e47649a --- /dev/null +++ b/exercise3/src/HashGrid.cpp @@ -0,0 +1,36 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "HashGrid.h" +#include + +void BuildHashGridFromTriangles(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize) +{ + std::cout << "Building hash grid from triangles .." << std::endl; + grid = HashGrid(cellSize, 1); + auto fend = m.faces_end(); + for(auto fit = m.faces_begin(); fit != fend; ++fit) + grid.Insert(Triangle(m,*fit)); + std::cout << "Done (using " << grid.NumCells() << " cells)." << std::endl; +} + +void BuildHashGridFromVertices(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize) +{ + std::cout << "Building hash grid from vertices .." << std::endl; + grid = HashGrid(cellSize, 1); + auto vend = m.vertices_end(); + for(auto vit = m.vertices_begin(); vit != vend; ++vit) + grid.Insert(Point(m,*vit)); + std::cout << "Done (using " << grid.NumCells() << " cells)." << std::endl; +} + +void BuildHashGridFromEdges(const HEMesh& m, HashGrid& grid, const Eigen::Vector3f& cellSize) +{ + std::cout << "Building hash grid from edges .." << std::endl; + grid = HashGrid(cellSize, 1); + auto eend = m.edges_end(); + for(auto eit = m.edges_begin(); eit != eend; ++eit) + grid.Insert(LineSegment(m,*eit)); + std::cout << "Done (using " << grid.NumCells() << " cells)." << std::endl; +} diff --git a/exercise3/src/LineSegment.cpp b/exercise3/src/LineSegment.cpp new file mode 100644 index 0000000..a098ceb --- /dev/null +++ b/exercise3/src/LineSegment.cpp @@ -0,0 +1,123 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "LineSegment.h" +#include "GridUtils.h" + + +//default constructor +LineSegment::LineSegment() +{ +} + +//constructs a line segment by the two end points v0, v1 without using the edge handle +LineSegment::LineSegment(const Eigen::Vector3f& v0, const Eigen::Vector3f& v1):v0(v0),v1(v1) +{ +} + +//construct a line segment from the edge e of the halfedge mesh m +LineSegment::LineSegment(const HEMesh& m,const OpenMesh::EdgeHandle& e):h(e) +{ + auto h = m.halfedge_handle(e, 0); + v0 = ToEigenVector(m.point(m.from_vertex_handle(h))); + v1 = ToEigenVector(m.point(m.to_vertex_handle(h))); +} + +//returns an axis aligned bounding box of the line segment +Box LineSegment::ComputeBounds() const +{ + Box b; + b.Insert(v0); + b.Insert(v1); + return b; +} + +//returns true if the line segment overlaps the given box b +bool LineSegment::Overlaps(const Box& b) const +{ + + Box aabb = ComputeBounds(); + + if(!b.Overlaps(aabb)) + return false; + + Eigen::Vector3f o = b.Center(); + Eigen::Vector3f u1 = v0-o; + Eigen::Vector3f u2 = v1-o; + + Eigen::Vector3f d1 = v1-v0; + d1.normalize(); + + float r = b.Radius(d1); + float lb = u1.dot(d1); + float ub = u2.dot(d1); + if(lb > ub) + std::swap(lb,ub); + if(lb > r || ub < -r) + return false; + + Eigen::Vector3f e1(1,0,0); + Eigen::Vector3f d2= d1.cross(e1); + r = b.Radius(d2); + lb = u1.dot(d2); + ub = u2.dot(d2); + if(lb > ub) + std::swap(lb,ub); + if(!OverlapIntervals(-r,r,lb,ub)) + return false; + + Eigen::Vector3f e2(0,1,0); + Eigen::Vector3f d3 = d1.cross(e2); + r = b.Radius(d3); + lb = u1.dot(d3); + ub = u2.dot(d3); + if(lb > ub) + std::swap(lb,ub); + if(!OverlapIntervals(-r,r,lb,ub)) + return false; + + Eigen::Vector3f e3(0,0,1); + Eigen::Vector3f d4 = d1.cross(e3); + r = b.Radius(d4); + lb = u1.dot(d4); + ub = u2.dot(d4); + + if(!OverlapIntervals(-r,r,lb,ub)) + return false; + + + return true; + + +} + +//returns the point with smallest distance topoint p which lies on the line segment +Eigen::Vector3f LineSegment::ClosestPoint(const Eigen::Vector3f& p) const +{ + //the two endpoints of the line segment are v0,v1 + /* Task 3.2.1 */ + return Eigen::Vector3f(0,0,0); +} + +//returns the squared distance between point p and the line segment +float LineSegment::SqrDistance(const Eigen::Vector3f& p) const +{ + Eigen::Vector3f d = p-ClosestPoint(p); + return d.squaredNorm(); +} + +//returns the euclidean distance between point p and the line segment +float LineSegment::Distance(const Eigen::Vector3f& p) const +{ + return sqrt(SqrDistance(p)); +} + +//returns a reference point which is on the line segment and is used to sort the primitive in the AABB tree construction +Eigen::Vector3f LineSegment::ReferencePoint() const +{ + return 0.5f*(v0 + v1); +} + + + diff --git a/exercise3/src/Point.cpp b/exercise3/src/Point.cpp new file mode 100644 index 0000000..fdbc37d --- /dev/null +++ b/exercise3/src/Point.cpp @@ -0,0 +1,65 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Point.h" +#include "GridUtils.h" + +//default constructor +Point::Point(){} + +//construct a point with given point position v0 +Point::Point(const Eigen::Vector3f& v0):v0(v0) +{ +} + +//construct a point from vertex v of giben halfedge mesh m +Point::Point(const HEMesh &m, const OpenMesh::VertexHandle& v):h(v) +{ + v0 = ToEigenVector(m.point(v)); +} + +//returns axis aligned bounding box of point +Box Point::ComputeBounds() const +{ + Box b; + b.Insert(v0); + + return b; +} + +//returns true if point overlap with box b +bool Point::Overlaps(const Box& b) const +{ + Eigen::Vector3f lb = b.LowerBound(); + Eigen::Vector3f ub = b.UpperBound(); + return + (v0[0] >= lb[0] && v0[0] <= ub[0]&& + v0[1] >= lb[1] && v0[1] <= ub[1] && + v0[2] >= lb[2] && v0[2] <= ub[2]); +} + +//returns the point position +Eigen::Vector3f Point::ClosestPoint(const Eigen::Vector3f& p) const +{ + return v0; +} + +//returns the squared distance between the query point p and the current point +float Point::SqrDistance(const Eigen::Vector3f& p) const +{ + Eigen::Vector3f d = p-ClosestPoint(p); + return d.squaredNorm(); +} + +//returns the euclidean distance between the query point p and the current point +float Point::Distance(const Eigen::Vector3f& p) const +{ + return sqrt(SqrDistance(p)); +} + +//returns a the position of the point as a reference point which is used to sort the primitive in the AABB tree construction +Eigen::Vector3f Point::ReferencePoint() const +{ + return v0; +} diff --git a/exercise3/src/Triangle.cpp b/exercise3/src/Triangle.cpp new file mode 100644 index 0000000..bb2a6d9 --- /dev/null +++ b/exercise3/src/Triangle.cpp @@ -0,0 +1,180 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Triangle.h" +#include "GridUtils.h" +#include + + +//default constructor +Triangle::Triangle() +{ +} +//constructs a triangle using the vertex positions v0,v1 and v2 +Triangle::Triangle(const Eigen::Vector3f& v0, const Eigen::Vector3f& v1,const Eigen::Vector3f& v2): v0(v0),v1(v1),v2(v2) +{ +} +//constructs a triangle from the face f of the given halfedge mesh m +Triangle::Triangle(const HEMesh&m, const OpenMesh::FaceHandle& f):h(f) +{ + OpenMesh::HalfedgeHandle he = m.halfedge_handle(f); + v0 = ToEigenVector(m.point(m.from_vertex_handle(he))); + he = m.next_halfedge_handle(he); + v1 = ToEigenVector(m.point(m.from_vertex_handle(he))); + he = m.next_halfedge_handle(he); + v2 = ToEigenVector(m.point(m.from_vertex_handle(he))); +} +//returns the smallest axis aligned bounding box of the triangle +Box Triangle::ComputeBounds() const +{ + /* Task 3.2.2 */ + Box b; + return b; +} + + +//returns true if the triangle overlaps the given box b +bool Triangle::Overlaps(const Box& b) const +{ + /* Task 3.2.2 */ + //carefully look at the interface of the box class, there might be a lot of useful helper functions + return true; +} +//returns the barycentric coordinates of the point with the smallest distance to point p which lies on the triangle +void Triangle::ClosestPointBarycentric(const Eigen::Vector3f& p, float& l0, float& l1, float& l2) const +{ + Eigen::Vector3f edge0 = v1 - v0; + Eigen::Vector3f edge1 = v2 - v0; + Eigen::Vector3f v = v0 - p; + + float a = edge0.dot( edge0 ); + float b = edge0.dot( edge1 ); + float c = edge1.dot( edge1 ); + float d = edge0.dot( v ); + float e = edge1.dot( v ); + + float det = a*c - b*b; + float s = b*e - c*d; + float t = b*d - a*e; + + if ( s + t < det ) + { + if ( s < 0.f ) + { + if ( t < 0.f ) + { + if ( d < 0.f ) + { + s=-d/a; + s=std::min(std::max(s,0.0f),1.0f); + t = 0.f; + } + else + { + s = 0.f; + t = -e/c; + t = std::min(std::max(t,0.0f),1.0f); + + } + } + else + { + s = 0.f; + t = -e/c; + t = std::min(std::max(t,0.0f),1.0f); + } + } + else if ( t < 0.f ) + { + s = -d/a; + s=std::min(std::max(s,0.0f),1.0f); + t = 0.f; + } + else + { + float invDet = 1.f / det; + s *= invDet; + t *= invDet; + } + } + else + { + if ( s < 0.f ) + { + float tmp0 = b+d; + float tmp1 = c+e; + if ( tmp1 > tmp0 ) + { + float numer = tmp1 - tmp0; + float denom = a-2*b+c; + s = numer/denom; + s=std::min(std::max(s,0.0f),1.0f); + t = 1-s; + } + else + { + t = -e/c; + t=std::min(std::max(t,0.0f),1.0f); + s = 0.f; + } + } + else if ( t < 0.f ) + { + if ( a+d > b+e ) + { + float numer = c+e-b-d; + float denom = a-2*b+c; + s = numer/denom; + s=std::min(std::max(s,0.0f),1.0f); + + t = 1-s; + } + else + { + s = -e/c; + s=std::min(std::max(s,0.0f),1.0f); + t = 0.f; + } + } + else + { + float numer = c+e-b-d; + float denom = a-2*b+c; + + s = numer/denom; + s=std::min(std::max(s,0.0f),1.0f); + t = 1.f - s; + } + } + l0 = 1-s-t; + l1 = s; + l2 = t; +} +//returns the point with smallest distance to point p which lies on the triangle +Eigen::Vector3f Triangle::ClosestPoint(const Eigen::Vector3f& p) const +{ + float l0,l1,l2; + ClosestPointBarycentric(p,l0,l1,l2); + return l0*v0 + l1*v1 +l2* v2; + +} +//returns the squared distance between point p and the triangle +float Triangle::SqrDistance(const Eigen::Vector3f& p) const +{ + Eigen::Vector3f d = p-ClosestPoint(p); + return d.squaredNorm(); +} +//returns the euclidean distance between point p and the triangle +float Triangle::Distance(const Eigen::Vector3f& p) const +{ + return sqrt(SqrDistance(p)); +} +//returns a reference point which is on the triangle and is used to sort the primitive in the AABB tree construction +Eigen::Vector3f Triangle::ReferencePoint() const +{ + return (v0+v1+v2)/3.0f; +} + + + diff --git a/exercise3/src/Viewer.cpp b/exercise3/src/Viewer.cpp new file mode 100644 index 0000000..e0f22e7 --- /dev/null +++ b/exercise3/src/Viewer.cpp @@ -0,0 +1,287 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Viewer.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include "GridTraverser.h" + +Viewer::Viewer() + : AbstractViewer("CG1 Exercise 3"), + renderer(polymesh), + closestPositions(nse::gui::VertexBuffer), + gridPositions(nse::gui::VertexBuffer), + rayPositions(nse::gui::VertexBuffer), rayCellsPositions(nse::gui::VertexBuffer) +{ + SetupGUI(); + + closestVAO.generate(); + gridVAO.generate(); + rayVAO.generate(); + rayCellsVAO.generate(); +} + +void Viewer::SetupGUI() +{ + auto mainWindow = SetupMainWindow(); + + auto loadFileBtn = new nanogui::Button(mainWindow, "Load Mesh"); + loadFileBtn->setCallback([this]() { + std::vector> fileTypes; + fileTypes.push_back(std::make_pair("obj", "OBJ File")); + auto file = nanogui::file_dialog(fileTypes, false); + if (!file.empty()) + { + polymesh.clear(); + if (!OpenMesh::IO::read_mesh(polymesh, file)) + { + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Load Mesh", + "The specified file could not be loaded"); + } + else + MeshUpdated(); + } + }); + + cmbPrimitiveType = new nanogui::ComboBox(mainWindow, { "Use Vertices", "Use Edges", "Use Triangles" }); + cmbPrimitiveType->setCallback([this](int) { FindClosestPoint(sldQuery->Value()); BuildGridVBO(); }); + + sldQuery = new nse::gui::VectorInput(mainWindow, "Query", Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), [this](const Eigen::Vector3f& p) { FindClosestPoint(p); }); + + sldRayOrigin = new nse::gui::VectorInput(mainWindow, "Ray Origin", Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), [this](const Eigen::Vector3f& p) { BuildRayVBOs(); }); + sldRayDir = new nse::gui::VectorInput(mainWindow, "Ray Direction", Eigen::Vector3f::Constant(-1), Eigen::Vector3f::Constant(1), Eigen::Vector3f::Zero(), [this](const Eigen::Vector3f& p) { BuildRayVBOs(); }); + nanogui::TextBox* txtRaySteps; + auto sldRaySteps = nse::gui::AddLabeledSlider(mainWindow, "Ray Steps", std::make_pair(1, 200), 80, txtRaySteps); + sldRaySteps->setCallback([this, txtRaySteps](float value) { + raySteps = (int)std::round(value); + txtRaySteps->setValue(std::to_string(raySteps)); + BuildRayVBOs(); + }); + sldRaySteps->callback()(sldRaySteps->value()); + + chkRenderMesh = new nanogui::CheckBox(mainWindow, "Render Mesh"); chkRenderMesh->setChecked(true); + chkRenderGrid = new nanogui::CheckBox(mainWindow, "Render Non-Empty Grid Cells"); chkRenderGrid->setChecked(false); + chkRenderRay = new nanogui::CheckBox(mainWindow, "Render Ray"); chkRenderRay->setChecked(false); + + shadingBtn = new nanogui::ComboBox(mainWindow, { "Smooth Shading", "Flat Shading" }); + + performLayout(); +} + +void Viewer::FindClosestPoint(const Eigen::Vector3f& p) +{ + if (polymesh.vertices_empty()) + return; + Eigen::Vector3f closest; + auto timeStart = std::chrono::high_resolution_clock::now(); + switch (cmbPrimitiveType->selectedIndex()) + { + case Vertex: + closest = vertexTree.ClosestPoint(p); + break; + case Edge: + closest = edgeTree.ClosestPoint(p); + break; + case Tri: + closest = triangleTree.ClosestPoint(p); + break; + } + auto timeEnd = std::chrono::high_resolution_clock::now(); + std::cout << std::fixed << "Closest point query took " << std::chrono::duration_cast(timeEnd - timeStart).count() << " microseconds." << std::endl; + + Eigen::Matrix4Xf points(4, 2); + points.block<3, 1>(0, 0) = p; + points.block<3, 1>(0, 1) = closest; + points.row(3).setConstant(1); + + closestVAO.bind(); + ShaderPool::Instance()->simpleShader.bind(); + closestPositions.uploadData(points).bindToAttribute("position"); + closestVAO.unbind(); +} + +void Viewer::MeshUpdated() +{ + //calculate the bounding Box of the mesh + nse::math::BoundingBox bbox; + for (auto v : polymesh.vertices()) + bbox.expand(ToEigenVector(polymesh.point(v))); + camera().FocusOnBBox(bbox); + bboxMaxLength = bbox.diagonal().maxCoeff(); + + polymesh.triangulate(); + + BuildAABBTreeFromVertices(polymesh, vertexTree); + BuildAABBTreeFromEdges(polymesh, edgeTree); + BuildAABBTreeFromTriangles(polymesh, triangleTree); + + Eigen::Vector3f cellSize = Eigen::Vector3f::Constant(bbox.diagonal().maxCoeff() / 50); + BuildHashGridFromVertices(polymesh, vertexGrid, cellSize); + BuildHashGridFromEdges(polymesh, edgeGrid, cellSize); + BuildHashGridFromTriangles(polymesh, triangleGrid, cellSize); + + sldQuery->SetBounds(bbox.min, bbox.max); + sldQuery->SetValue(bbox.max); + FindClosestPoint(bbox.max); + + sldRayOrigin->SetBounds(bbox.min, bbox.max); + sldRayOrigin->SetValue(bbox.min); + sldRayDir->SetValue(bbox.diagonal().normalized()); + + BuildGridVBO(); + + renderer.Update(); +} + +void Viewer::BuildGridVBO() +{ + switch (cmbPrimitiveType->selectedIndex()) + { + case Vertex: + BuildGridVBO(vertexGrid); + break; + case Edge: + BuildGridVBO(edgeGrid); + break; + case Tri: + BuildGridVBO(triangleGrid); + break; + } +} + +void Viewer::BuildRayVBOs() +{ + if (polymesh.vertices_empty()) + return; + + //Ray line indicator + Eigen::Matrix4Xf rayPoints(4, 2); + rayPoints.block<3, 1>(0, 0) = sldRayOrigin->Value(); + rayPoints.block<3, 1>(0, 1) = sldRayOrigin->Value() + sldRayDir->Value().normalized() * 0.2f * bboxMaxLength; + rayPoints.row(3).setConstant(1); + + rayVAO.bind(); + ShaderPool::Instance()->simpleShader.bind(); + rayPositions.uploadData(rayPoints).bindToAttribute("position"); + rayVAO.unbind(); + + //Ray cells + std::vector cellPositions; + GridTraverser trav(sldRayOrigin->Value(), sldRayDir->Value(), vertexGrid.CellExtents()); + for (int i = 0; i < raySteps; ++i, trav++) + { + auto bounds = vertexGrid.CellBounds(*trav); + AddBoxVertices(bounds, cellPositions); + } + + rayCellsVAO.bind(); + rayCellsPositions.uploadData(cellPositions).bindToAttribute("position"); + rayCellsVAO.unbind(); + rayCellsIndices = (GLuint)cellPositions.size(); +} + +void Viewer::AddBoxVertices(const Box & box, std::vector& positions) +{ + auto& lb = box.LowerBound(); + auto& ub = box.UpperBound(); + Eigen::Vector4f o; o << lb, 1.0f; + Eigen::Vector4f x; x << ub.x() - lb.x(), 0, 0, 0; + Eigen::Vector4f y; y << 0, ub.y() - lb.y(), 0, 0; + Eigen::Vector4f z; z << 0, 0, ub.z() - lb.z(), 0; + positions.push_back(o); + positions.push_back(o + x); + positions.push_back(o + x); + positions.push_back(o + x + y); + positions.push_back(o + x + y); + positions.push_back(o + y); + positions.push_back(o + y); + positions.push_back(o); + + positions.push_back(o + z); + positions.push_back(o + z + x); + positions.push_back(o + z + x); + positions.push_back(o + z + x + y); + positions.push_back(o + z + x + y); + positions.push_back(o + z + y); + positions.push_back(o + z + y); + positions.push_back(o + z); + + positions.push_back(o); + positions.push_back(o + z); + positions.push_back(o + x); + positions.push_back(o + x + z); + positions.push_back(o + y); + positions.push_back(o + y + z); + positions.push_back(o + x + y); + positions.push_back(o + x + y + z); +} + +void Viewer::drawContents() +{ + glEnable(GL_DEPTH_TEST); + + if (!polymesh.vertices_empty()) + { + Eigen::Matrix4f view, proj; + camera().ComputeCameraMatrices(view, proj); + Eigen::Matrix4f mvp = proj * view; + + if(chkRenderMesh->checked()) + renderer.Render(view, proj, shadingBtn->selectedIndex() == 1); + + ShaderPool::Instance()->simpleShader.bind(); + ShaderPool::Instance()->simpleShader.setUniform("mvp", mvp); + + //Draw line between query point and its closest position + closestVAO.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(1, 1, 1, 1)); + glDrawArrays(GL_LINES, 0, 2); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(1, 0, 0, 1)); + glPointSize(3.0f); + glDrawArrays(GL_POINTS, 0, 2); + closestVAO.unbind(); + + //Draw non-empty grid cells + if (gridIndices > 0 && chkRenderGrid->checked()) + { + gridVAO.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(0.2f, 0.2f, 0.2f, 1)); + glDrawArrays(GL_LINES, 0, gridIndices); + gridVAO.unbind(); + } + + if (chkRenderRay->checked()) + { + //Draw line for ray + rayVAO.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(1, 1, 1, 1)); + glDrawArrays(GL_LINES, 0, 2); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(0, 1, 0, 1)); + glPointSize(3.0f); + glDrawArrays(GL_POINTS, 0, 1); + rayVAO.unbind(); + + //Draw ray cells + rayCellsVAO.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(0.0f, 0.8f, 0.0f, 1)); + glDrawArrays(GL_LINES, 0, rayCellsIndices); + rayCellsVAO.unbind(); + } + } +} \ No newline at end of file diff --git a/exercise3/src/main.cpp b/exercise3/src/main.cpp new file mode 100644 index 0000000..e94f898 --- /dev/null +++ b/exercise3/src/main.cpp @@ -0,0 +1,38 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include + +#include + +#include "Viewer.h" + +int main(int argc, char* argv[]) +{ + std::cout.imbue(std::locale("")); + + nanogui::init(); + + { + nanogui::ref viewer = new Viewer(); + viewer->setVisible(true); + + nse::util::GLDebug::SetupDebugCallback(); + nse::util::GLDebug::IgnoreGLError(131185); //buffer usage info + + try + { + nanogui::mainloop(); + } + catch (std::runtime_error& e) + { + std::cerr << e.what() << std::endl; + } + + } + + nanogui::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/exercise4/CMakeLists.txt b/exercise4/CMakeLists.txt new file mode 100644 index 0000000..dc3fd08 --- /dev/null +++ b/exercise4/CMakeLists.txt @@ -0,0 +1,10 @@ +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) + +add_executable(Exercise4 MACOSX_BUNDLE + src/main.cpp + src/Viewer.cpp include/Viewer.h + src/Parametrization.cpp include/Parametrization.h + src/Registration.cpp include/Registration.h + ) + +target_link_libraries(Exercise4 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise4/include/Parametrization.h b/exercise4/include/Parametrization.h new file mode 100644 index 0000000..48d8f4d --- /dev/null +++ b/exercise4/include/Parametrization.h @@ -0,0 +1,28 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include + +//returns true if mesh is a topological disk +bool IsTopologicalDisk(const HEMesh& m, OpenMesh::HalfedgeHandle& outBoundary); + +//different possible weights +enum WeightType { CONSTANT_WEIGHT, EDGE_LENGTH_WEIGHT, INV_EDGE_LENGTH_WEIGHT, COTAN_WEIGHT}; + +//compute weight w_ij for a given halfedge, where vertex i is the origin and vertex j is the target of halfedge h +template +float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h); + +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h); +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h); +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h); +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h); + +//computes a parametrization (textcoords for each vertex) +//texcoords of boundary vertices are placed on a circle with center (0.5,0.5) and radius 1 intexture space +//texcoords of inner vertices are placed in the weighted mean of its one ring neighbors texcoords +//these constraints can be stacked into a sparse linear equation system which can be solved in a least squares sense +template +bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh); \ No newline at end of file diff --git a/exercise4/include/Registration.h b/exercise4/include/Registration.h new file mode 100644 index 0000000..5b42ccb --- /dev/null +++ b/exercise4/include/Registration.h @@ -0,0 +1,18 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once +#include +#include +#include + +//a point to point correspondence +// the first point is on mesh_A the second in mesh_B +typedef std::pair correspondence; + +//compute a rigid transformation (R,t) that maps the correspondences +//to each other in a least-squares sense (map second entry to first entry) +Eigen::Affine3f CalculateRigidRegistration(std::vector& correspondences); + + diff --git a/exercise4/include/Viewer.h b/exercise4/include/Viewer.h new file mode 100644 index 0000000..00dcbad --- /dev/null +++ b/exercise4/include/Viewer.h @@ -0,0 +1,49 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#pragma once + +#include +#include +#include + +#include "Registration.h" +#include + +class Viewer : public nse::gui::AbstractViewer +{ +public: + Viewer(); + + void drawContents(); + + virtual bool resizeEvent(const Eigen::Vector2i&); + +private: + + void SetupGUI(); + void MeshUpdated(); + void BuildCorrVBOs(); + + nse::math::BoundingBox meshBbox, expandedBbox; + + nanogui::ComboBox* shadingBtn; + nanogui::CheckBox* chkRenderTextureMap; + nanogui::CheckBox* chkRenderSecondMesh; + + HEMesh polymesh; + MeshRenderer renderer; + + bool hasParametrization = false; + Eigen::Matrix4f texMapProjectionMatrix; + + Eigen::Affine3f secondMeshTransform; + + std::mt19937 rnd; + std::vector correspondences; + + size_t corrCount; + nse::gui::GLBuffer corrPositions; + nse::gui::GLVertexArray corrVAO; +}; diff --git a/exercise4/src/Parametrization.cpp b/exercise4/src/Parametrization.cpp new file mode 100644 index 0000000..1077a88 --- /dev/null +++ b/exercise4/src/Parametrization.cpp @@ -0,0 +1,92 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Parametrization.h" +#include +#include +#include +#include + +bool IsTopologicalDisk(const HEMesh& m, OpenMesh::HalfedgeHandle& outBoundary) +{ + std::vector vertexVisited(m.n_vertices()); + std::queue vQueue; + vQueue.push(m.vertex_handle(0)); + size_t visitedVertices = 0; + size_t visitedBoundaries = 0; + + //check if we can reach all vertices from the first vertex by region growing + while (!vQueue.empty()) + { + auto v = vQueue.front(); + vQueue.pop(); + + if (vertexVisited[v.idx()]) + continue; + vertexVisited[v.idx()] = true; + ++visitedVertices; + + //visit all the vertices along the boundary + if (m.is_boundary(v)) + { + ++visitedBoundaries; + auto h = m.halfedge_handle(v); //this will always be a boundary halfedge + outBoundary = h; + auto nextV = m.to_vertex_handle(h); + while (!vertexVisited[nextV.idx()]) + { + vertexVisited[nextV.idx()] = true; + ++visitedVertices; + vQueue.push(nextV); + h = m.next_halfedge_handle(h); + nextV = m.to_vertex_handle(h); + } + } + + for (auto vn : m.vv_range(v)) + vQueue.push(vn); + } + + return visitedVertices == m.n_vertices() && visitedBoundaries == 1; +} + +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h) +{ + return 0; +} + +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h) +{ + return 0; +} + +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h) +{ + return 0; +} + +template<> float Weight(HEMesh& m, OpenMesh::HalfedgeHandle h) +{ + return 0; +} + +template +bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh) +{ + OpenMesh::HalfedgeHandle boundary; + if (!IsTopologicalDisk(mesh, boundary)) + { + std::cout << "This mesh is not a topological disk." << std::endl; + return false; + } + + /* Task 4.2.1 */ + + return true; +} + +template bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh); +template bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh); +template bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh); +template bool ComputeParametrizationOfTopologicalDisk(HEMesh& mesh); \ No newline at end of file diff --git a/exercise4/src/Registration.cpp b/exercise4/src/Registration.cpp new file mode 100644 index 0000000..c86ae95 --- /dev/null +++ b/exercise4/src/Registration.cpp @@ -0,0 +1,18 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Registration.h" + +Eigen::Affine3f CalculateRigidRegistration(std::vector& correspondences) +{ + //transform to compute + Eigen::Affine3f T = Eigen::Affine3f(Eigen::Matrix3f::Identity()); + + if(correspondences.size() < 3) + return T; + + /* Task 4.2.2 */ + + return T; +} diff --git a/exercise4/src/Viewer.cpp b/exercise4/src/Viewer.cpp new file mode 100644 index 0000000..92ba141 --- /dev/null +++ b/exercise4/src/Viewer.cpp @@ -0,0 +1,267 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include "Viewer.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "Parametrization.h" +#include "Registration.h" + +#include + +Viewer::Viewer() + : AbstractViewer("CG1 Exercise 4"), + renderer(polymesh), + corrPositions(nse::gui::VertexBuffer) +{ + polymesh.request_vertex_texcoords2D(); + + corrVAO.generate(); + + SetupGUI(); +} + +void Viewer::SetupGUI() +{ + auto mainWindow = SetupMainWindow(); + + auto loadFileBtn = new nanogui::Button(mainWindow, "Load Mesh"); + loadFileBtn->setCallback([this]() { + std::vector> fileTypes; + fileTypes.push_back(std::make_pair("obj", "OBJ File")); + auto file = nanogui::file_dialog(fileTypes, false); + if (!file.empty()) + { + polymesh.clear(); + if (!OpenMesh::IO::read_mesh(polymesh, file)) + { + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Load Mesh", + "The specified file could not be loaded"); + } + else + MeshUpdated(); + } + }); + + shadingBtn = new nanogui::ComboBox(mainWindow, { "Smooth Shading", "Flat Shading" }); + + performLayout(); + + auto paramWindow = new nanogui::Window(this, "Parametrization"); + paramWindow->setPosition(Eigen::Vector2i(mainWindow->position().x(), mainWindow->position().y() + mainWindow->size().y() + 15)); + paramWindow->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); + + auto cmbWeightType = new nanogui::ComboBox(paramWindow, { "Constant Weight", "Edge Length Weight", "Inverse Edge Length Weight", "Cotan Weight" }); + + auto parametrizeBtn = new nanogui::Button(paramWindow, "Calculate Parametrization"); + parametrizeBtn->setCallback([this, cmbWeightType]() { + if (polymesh.n_vertices() == 0) + { + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Parametrization", "Please load a mesh for parametrization."); + } + else + { + bool success; + switch (cmbWeightType->selectedIndex()) + { + case 0: + success = ComputeParametrizationOfTopologicalDisk(polymesh); + break; + case 1: + success = ComputeParametrizationOfTopologicalDisk(polymesh); + break; + case 2: + success = ComputeParametrizationOfTopologicalDisk(polymesh); + break; + case 3: + success = ComputeParametrizationOfTopologicalDisk(polymesh); + break; + } + if (success) + { + renderer.Update(); + hasParametrization = true; + } + else + new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Parametrization", "Parametrization failed."); + } + }); + + chkRenderTextureMap = new nanogui::CheckBox(paramWindow, "Render Texture Map"); + + performLayout(); + + auto registrationWindow = new nanogui::Window(this, "Registration"); + registrationWindow->setPosition(Eigen::Vector2i(paramWindow->position().x(), paramWindow->position().y() + paramWindow->size().y() + 15)); + registrationWindow->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); + + auto rotateBtn = new nanogui::Button(registrationWindow, "Random Rotation"); + rotateBtn->setCallback([this]() { + secondMeshTransform = Eigen::Affine3f(Eigen::Translation3f(3 * meshBbox.diagonal().x(), 0, 0)); + std::uniform_real_distribution dist(-1.0f, 1.0f); + Eigen::Quaternionf q(dist(rnd), dist(rnd), dist(rnd), dist(rnd)); + q.normalize(); + secondMeshTransform *= q; + + correspondences.clear(); + BuildCorrVBOs(); + }); + + corrCount = 10; + nanogui::TextBox* txtCorrCount; + auto sldCorrCount = nse::gui::AddLabeledSlider(registrationWindow, "Correspondence Count", std::make_pair(1.0f, 50.0f), (float)corrCount, txtCorrCount); + sldCorrCount->setCallback([this, txtCorrCount](float value) { + corrCount = (int)std::round(value); + txtCorrCount->setValue(std::to_string(corrCount)); + }); + sldCorrCount->callback()(sldCorrCount->value()); + + auto autoCorrs = new nanogui::Button(registrationWindow, "Automatic Correspondences"); + autoCorrs->setCallback([this]() { + if (corrCount > polymesh.n_vertices()) + return; + std::uniform_int_distribution dist(0, (int)polymesh.n_vertices()); + + correspondences.clear(); + + for (int i = 0; i < corrCount; ++i) + { + auto v = polymesh.vertex_handle(dist(rnd)); + auto p = ToEigenVector(polymesh.point(v)); + correspondences.push_back(std::make_pair(p, secondMeshTransform * p)); + } + + BuildCorrVBOs(); + }); + + auto distortCorrespondencesBtn = new nanogui::Button(registrationWindow, "Distort Correspondences"); + distortCorrespondencesBtn->setCallback([this]() { + std::normal_distribution dist(0.0f, meshBbox.diagonal().norm() * 0.01f); + for (auto& c : correspondences) + c.first += Eigen::Vector3f(dist(rnd), dist(rnd), dist(rnd)); + BuildCorrVBOs(); + }); + + auto registerBtn = new nanogui::Button(registrationWindow, "Register"); + registerBtn->setCallback([this]() { + auto T = CalculateRigidRegistration(correspondences); + secondMeshTransform = T * secondMeshTransform; + + for (auto& c : correspondences) + c.second = T * c.second; + BuildCorrVBOs(); + }); + + chkRenderSecondMesh = new nanogui::CheckBox(registrationWindow, "Render Second Mesh", [this](bool checked) { + if (checked) + camera().FocusOnBBox(expandedBbox); + else + camera().FocusOnBBox(meshBbox); + }); + + performLayout(); + + auto maxWidth = std::max(mainWindow->size().x(), std::max(paramWindow->size().x(), registrationWindow->size().x())); + mainWindow->setFixedWidth(maxWidth); + paramWindow->setFixedWidth(maxWidth); + registrationWindow->setFixedWidth(maxWidth); + + performLayout(); +} + +void Viewer::MeshUpdated() +{ + //calculate the bounding Box of the mesh + meshBbox.reset(); + for (auto v : polymesh.vertices()) + meshBbox.expand(ToEigenVector(polymesh.point(v))); + + expandedBbox = meshBbox; + secondMeshTransform = Eigen::Affine3f(Eigen::Translation3f(3 * meshBbox.diagonal().x(), 0, 0)); + expandedBbox.max.x() += 3 * meshBbox.diagonal().x(); + + if(chkRenderSecondMesh->checked()) + camera().FocusOnBBox(expandedBbox); + else + camera().FocusOnBBox(meshBbox); + + polymesh.triangulate(); + correspondences.clear(); + + hasParametrization = false; + + renderer.Update(); + BuildCorrVBOs(); +} + +void Viewer::BuildCorrVBOs() +{ + std::vector pos; + pos.reserve(correspondences.size() * 2); + for (auto& c : correspondences) + { + pos.push_back(Eigen::Vector4f(c.first.x(), c.first.y(), c.first.z(), 1.0f)); + pos.push_back(Eigen::Vector4f(c.second.x(), c.second.y(), c.second.z(), 1.0f)); + } + + corrVAO.bind(); + ShaderPool::Instance()->simpleShader.bind(); + corrPositions.uploadData(pos).bindToAttribute("position"); + corrVAO.unbind(); +} + +bool Viewer::resizeEvent(const Eigen::Vector2i& size) +{ + AbstractViewer::resizeEvent(size); + + float ratio = (float)size.x() / size.y(); + Eigen::Affine3f proj = Eigen::Translation3f(1.0f - 0.1f / ratio - 1.8f / ratio, -0.9f, 0.0f) * Eigen::AlignedScaling3f(1.8f / ratio, 1.8f, 1.0f); + texMapProjectionMatrix = proj.matrix(); + + return true; +} + +void Viewer::drawContents() +{ + glEnable(GL_DEPTH_TEST); + + if (!polymesh.vertices_empty()) + { + Eigen::Matrix4f view, proj; + camera().ComputeCameraMatrices(view, proj); + Eigen::Matrix4f mvp = proj * view; + + renderer.Render(view, proj, shadingBtn->selectedIndex() == 1, hasParametrization); + + if (chkRenderSecondMesh->checked()) + { + renderer.Render(view * secondMeshTransform.matrix(), proj, shadingBtn->selectedIndex() == 1, false, Eigen::Vector4f(0.2f, 0.3f, 0.4f, 1.0f)); + if (correspondences.size() > 0) + { + corrVAO.bind(); + ShaderPool::Instance()->simpleShader.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(1, 1, 1, 1)); + ShaderPool::Instance()->simpleShader.setUniform("mvp", mvp); + glDrawArrays(GL_LINES, 0, (GLsizei)correspondences.size() * 2); + corrVAO.unbind(); + } + } + + if (hasParametrization && chkRenderTextureMap->checked()) + { + glDisable(GL_DEPTH_TEST); + renderer.RenderTextureMap(texMapProjectionMatrix, Eigen::Vector4f(1, 1, 1, 1)); + } + } +} \ No newline at end of file diff --git a/exercise4/src/main.cpp b/exercise4/src/main.cpp new file mode 100644 index 0000000..e94f898 --- /dev/null +++ b/exercise4/src/main.cpp @@ -0,0 +1,38 @@ +// This source code is property of the Computer Graphics and Visualization +// chair of the TU Dresden. Do not distribute! +// Copyright (C) CGV TU Dresden - All Rights Reserved + +#include + +#include + +#include "Viewer.h" + +int main(int argc, char* argv[]) +{ + std::cout.imbue(std::locale("")); + + nanogui::init(); + + { + nanogui::ref viewer = new Viewer(); + viewer->setVisible(true); + + nse::util::GLDebug::SetupDebugCallback(); + nse::util::GLDebug::IgnoreGLError(131185); //buffer usage info + + try + { + nanogui::mainloop(); + } + catch (std::runtime_error& e) + { + std::cerr << e.what() << std::endl; + } + + } + + nanogui::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/ext/OpenMesh b/ext/OpenMesh new file mode 160000 index 0000000..7b59f6e --- /dev/null +++ b/ext/OpenMesh @@ -0,0 +1 @@ +Subproject commit 7b59f6e07d68f262f3beac8e0ac14f3ce72bf073 diff --git a/ext/nanogui b/ext/nanogui new file mode 160000 index 0000000..97b1526 --- /dev/null +++ b/ext/nanogui @@ -0,0 +1 @@ +Subproject commit 97b15265f4459f649cb0da652ce327d0f6a815b9