diff --git a/CMakeLists.txt b/CMakeLists.txt index d9fca8c..7c9477c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,3 +48,4 @@ add_subdirectory(exercise1) add_subdirectory(exercise2) add_subdirectory(exercise3) add_subdirectory(exercise4) +add_subdirectory(exercise5) diff --git a/common/include/gui/Camera.h b/common/include/gui/Camera.h index 20dd595..8cb7b34 100644 --- a/common/include/gui/Camera.h +++ b/common/include/gui/Camera.h @@ -33,7 +33,7 @@ namespace nse { Eigen::Matrix4f &proj, float customAspectRatio = 0) const; - //Applies a zoom by scaling the scene. Positive values of amount increase object sizes. + //Applies a zoom by changing the view distance. Positive values of amount decrease the distance. void Zoom(float amount); //Sets the extent of the scene, which is kept between znear/zfar. @@ -64,6 +64,8 @@ namespace nse { //Forwarded resize event. void resize(const Eigen::Vector2i & s); + void FixClippingPlanes(float znear, float zfar); + //Returns the point that the camera focuses on const Eigen::Vector3f& GetFocusPoint() const; @@ -99,6 +101,8 @@ namespace nse { Eigen::Vector3f modelTranslation_start = Eigen::Vector3f::Zero(); Eigen::Vector2i translation_start; //mouse position on the screen where translation started + + float fixedZNear, fixedZFar; }; } } \ No newline at end of file diff --git a/common/src/gui/Camera.cpp b/common/src/gui/Camera.cpp index 77eff96..1f14a58 100644 --- a/common/src/gui/Camera.cpp +++ b/common/src/gui/Camera.cpp @@ -14,7 +14,7 @@ using namespace nse::gui; Camera::Camera(const nanogui::Widget & parent) - : parent(parent) + : parent(parent), fixedZNear(std::numeric_limits::quiet_NaN()) { params.arcball.setSize(parent.size()); } @@ -27,8 +27,17 @@ void Camera::ComputeCameraMatrices(Eigen::Matrix4f & view, Eigen::Matrix4f & pro 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 znear, zfar; + if (std::isnan(fixedZNear)) + { + znear = std::max(minZNear, depthOfSceneCenter - params.sceneRadius); + zfar = std::max(znear + minZNear, depthOfSceneCenter + params.sceneRadius); + } + else + { + znear = fixedZNear; + zfar = fixedZFar; + } float fH = std::tan(params.fovy / 360.0f * (float)M_PI) * znear; float aspectRatio = customAspectRatio == 0 ? (float)parent.width() / parent.height() : customAspectRatio; @@ -46,6 +55,7 @@ void Camera::Zoom(float amount) void Camera::SetSceneExtent(const nse::math::BoundingBox& bbox) { + fixedZNear = std::numeric_limits::quiet_NaN(); params.sceneCenter = 0.5f * (bbox.min + bbox.max); params.sceneRadius = bbox.diagonal().norm() / 2.0f; } @@ -174,4 +184,10 @@ bool Camera::HandleMouseMove(const Eigen::Vector2i & p, const Eigen::Vector2i & void Camera::resize(const Eigen::Vector2i & s) { params.arcball.setSize(s); +} + +void Camera::FixClippingPlanes(float znear, float zfar) +{ + fixedZNear = znear; + fixedZFar = zfar; } \ No newline at end of file diff --git a/common/src/util/GLDebug.cpp b/common/src/util/GLDebug.cpp index dd8699e..d4213de 100644 --- a/common/src/util/GLDebug.cpp +++ b/common/src/util/GLDebug.cpp @@ -132,5 +132,8 @@ void nse::util::GLDebug::SetupDebugCallback() { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(DebugCallback, &ignoredIds); + if (glDebugMessageCallback == nullptr) + std::cout << "Cannot set up an OpenGL debug callback. Perhaps your OpenGL version is too old." << std::endl; + else + glDebugMessageCallback(DebugCallback, &ignoredIds); } \ No newline at end of file diff --git a/exercise2/CMakeLists.txt b/exercise2/CMakeLists.txt index 6c9fb4d..0df7f7e 100644 --- a/exercise2/CMakeLists.txt +++ b/exercise2/CMakeLists.txt @@ -1,14 +1,31 @@ +set(GLSL_FILES sky.vert sky.frag + terrain.vert terrain.frag) + +ProcessGLSLFiles(GLSL_FILES) + +set(TEXTURE_FILES grass.jpg rock.jpg roadColor.jpg roadNormals.jpg roadSpecular.jpg alpha.jpg) +PREPEND(TEXTURE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/resources/" ${TEXTURE_FILES}) +JOIN("${TEXTURE_FILES}" "," texture_string) +set(bin2c_cmdline + -DOUTPUT_C=textures.cpp + -DOUTPUT_H=textures.h + "-DINPUT_FILES=${texture_string}" + -P "${NANOGUI_DIR}/resources/bin2c.cmake") + + add_custom_command( + OUTPUT textures.cpp textures.h + COMMAND ${CMAKE_COMMAND} ARGS ${bin2c_cmdline} + DEPENDS ${TEXTURE_FILES} + COMMENT "Running bin2c" + PRE_BUILD VERBATIM) + include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) add_executable(Exercise2 MACOSX_BUNDLE + glsl.cpp + textures.cpp 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) + ${GLSL_FILES}) target_link_libraries(Exercise2 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise2/glsl/sky.frag b/exercise2/glsl/sky.frag new file mode 100644 index 0000000..839a02e --- /dev/null +++ b/exercise2/glsl/sky.frag @@ -0,0 +1,21 @@ +// 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 330 + +out vec4 color; + +in vec4 clipPos; + +const vec4 horizon = vec4(0.85, 0.85, 0.8, 1.0); +const vec4 floor = vec4(0.1, 0.1, 0.1, 1.0); +const vec4 sky = vec4(0.5, 0.6, 0.8, 1.0); + +void main() +{ + float h = normalize(clipPos.xyz).y; + if(h < 0) + color = mix(horizon, floor, pow(-h, 0.5)); + else + color = mix(horizon, sky, pow(h, 0.9)); +} \ No newline at end of file diff --git a/exercise2/glsl/sky.vert b/exercise2/glsl/sky.vert new file mode 100644 index 0000000..b154cce --- /dev/null +++ b/exercise2/glsl/sky.vert @@ -0,0 +1,19 @@ +// 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 330 + +uniform mat4 mvp; + +out vec4 clipPos; + +void main() +{ + clipPos = vec4( float((gl_VertexID << 1) & 2) - 1.0, + float((gl_VertexID + 1) & 2) - 1.0, + float( gl_VertexID & 2) - 1.0, + 1.0); + + gl_Position = mvp * clipPos; + gl_Position.z = 0.5 * gl_Position.w; +} \ No newline at end of file diff --git a/exercise2/glsl/terrain.frag b/exercise2/glsl/terrain.frag new file mode 100644 index 0000000..68e8105 --- /dev/null +++ b/exercise2/glsl/terrain.frag @@ -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 +#version 330 + + + +out vec4 color; + +uniform vec3 cameraPos; + + +uniform sampler2D background; +uniform vec2 screenSize; + +const vec3 dirToLight = normalize(vec3(1, 3, 1)); + +//Calculates the visible surface color based on the Blinn-Phong illumination model +vec4 calculateLighting(vec4 materialColor, float specularIntensity, vec3 normalizedNormal, vec3 directionToViewer) +{ + vec4 color = materialColor; + vec3 h = normalize(dirToLight + directionToViewer); + color.xyz *= 0.9 * max(dot(normalizedNormal, dirToLight), 0) + 0.1; + color.xyz += specularIntensity * pow(max(dot(h, normalizedNormal), 0), 50); + return color; +} + +vec4 getBackgroundColor() +{ + return texture(background, gl_FragCoord.xy / screenSize); +} + +void main() +{ + //surface geometry + vec3 n = vec3(0, 1, 0); + vec3 dirToViewer = vec3(0, 1, 0); + + //material properties + color = vec4(0.6, 0.6, 0.6, 1); + float specular = 0; + + + + //Calculate light + color = calculateLighting(color, specular, n, dirToViewer); + + +} \ No newline at end of file diff --git a/exercise2/glsl/terrain.vert b/exercise2/glsl/terrain.vert new file mode 100644 index 0000000..79f3139 --- /dev/null +++ b/exercise2/glsl/terrain.vert @@ -0,0 +1,58 @@ +// 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 330 + +in vec4 position; + + + +uniform mat4 mvp; + +//Returns the height of the procedural terrain at a given xz position +float getTerrainHeight(vec2 p); + + + +void main() +{ + gl_Position = mvp * position; +} + +//source: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 +float rand(vec2 c) +{ + return 2 * fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453) - 1; +} + +float perlinNoise(vec2 p ) +{ + vec2 ij = floor(p); + vec2 xy = p - ij; + //xy = 3.*xy*xy-2.*xy*xy*xy; + xy = .5*(1.-cos(3.1415926 * xy)); + float a = rand((ij+vec2(0.,0.))); + float b = rand((ij+vec2(1.,0.))); + float c = rand((ij+vec2(0.,1.))); + float d = rand((ij+vec2(1.,1.))); + float x1 = mix(a, b, xy.x); + float x2 = mix(c, d, xy.x); + return mix(x1, x2, xy.y); +} + +//based on https://www.seedofandromeda.com/blogs/58-procedural-heightmap-terrain-generation +float getTerrainHeight(vec2 p) +{ + float total = 0.0; + float maxAmplitude = 0.0; + float amplitude = 1.0; + float frequency = 0.02; + for (int i = 0; i < 11; i++) + { + total += ((1.0 - abs(perlinNoise(p * frequency))) * 2.0 - 1.0) * amplitude; + frequency *= 2.0; + maxAmplitude += amplitude; + amplitude *= 0.45; + } + return 15 * total / maxAmplitude; +} \ No newline at end of file diff --git a/exercise2/include/Viewer.h b/exercise2/include/Viewer.h index 29d694b..0a17718 100644 --- a/exercise2/include/Viewer.h +++ b/exercise2/include/Viewer.h @@ -5,31 +5,38 @@ #pragma once #include -#include +#include +#include +#include class Viewer : public nse::gui::AbstractViewer { public: Viewer(); - void drawContents(); + void LoadShaders(); + void CreateGeometry(); -private: - void SetupGUI(); - void MeshUpdated(bool initNewMesh = false); + void drawContents(); + bool resizeEvent(const Eigen::Vector2i&); - void ColorMeshFromIds(); +private: - bool hasColors = false; + void RenderSky(); - nanogui::ComboBox* shadingBtn; - unsigned int smoothingIterations; - nanogui::Slider* sldSmoothingStrength; - unsigned int stripificationTrials; + Eigen::Matrix4f view, proj; - HEMesh polymesh; - MeshRenderer renderer; + nse::gui::GLShader skyShader; + nse::gui::GLVertexArray emptyVAO; - OpenMesh::FPropHandleT faceIdProperty; - OpenMesh::FPropHandleT faceColorProperty; + nse::gui::GLShader terrainShader; + nse::gui::GLVertexArray terrainVAO; + nse::gui::GLBuffer terrainPositions; + nse::gui::GLBuffer terrainIndices; + + GLuint grassTexture, rockTexture, roadColorTexture, roadNormalMap, roadSpecularMap, alphaMap; + + nse::gui::GLBuffer offsetBuffer; + + GLuint backgroundFBO, backgroundTexture; }; diff --git a/exercise2/resources/alpha.jpg b/exercise2/resources/alpha.jpg new file mode 100644 index 0000000..938c697 Binary files /dev/null and b/exercise2/resources/alpha.jpg differ diff --git a/exercise2/resources/grass.jpg b/exercise2/resources/grass.jpg new file mode 100644 index 0000000..eb972b6 Binary files /dev/null and b/exercise2/resources/grass.jpg differ diff --git a/exercise2/resources/roadColor.jpg b/exercise2/resources/roadColor.jpg new file mode 100644 index 0000000..4c7a7d2 Binary files /dev/null and b/exercise2/resources/roadColor.jpg differ diff --git a/exercise2/resources/roadNormals.jpg b/exercise2/resources/roadNormals.jpg new file mode 100644 index 0000000..204fa93 Binary files /dev/null and b/exercise2/resources/roadNormals.jpg differ diff --git a/exercise2/resources/roadSpecular.jpg b/exercise2/resources/roadSpecular.jpg new file mode 100644 index 0000000..67b3b02 Binary files /dev/null and b/exercise2/resources/roadSpecular.jpg differ diff --git a/exercise2/resources/rock.jpg b/exercise2/resources/rock.jpg new file mode 100644 index 0000000..002e01f Binary files /dev/null and b/exercise2/resources/rock.jpg differ diff --git a/exercise2/src/Viewer.cpp b/exercise2/src/Viewer.cpp index dd3b377..fb7accd 100644 --- a/exercise2/src/Viewer.cpp +++ b/exercise2/src/Viewer.cpp @@ -5,251 +5,182 @@ #include "Viewer.h" #include -#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" +#include -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 }, +#include -}; +#include "glsl.h" +#include "textures.h" + +const uint32_t PATCH_SIZE = 256; //number of vertices along one side of the terrain patch Viewer::Viewer() : AbstractViewer("CG1 Exercise 2"), - renderer(polymesh) + terrainPositions(nse::gui::VertexBuffer), terrainIndices(nse::gui::IndexBuffer), + offsetBuffer(nse::gui::VertexBuffer) { - SetupGUI(); + LoadShaders(); + CreateGeometry(); + + //Create a texture and framebuffer for the background + glGenFramebuffers(1, &backgroundFBO); + glGenTextures(1, &backgroundTexture); - polymesh.add_property(faceIdProperty); - polymesh.add_property(faceColorProperty); + //Align camera to view a reasonable part of the terrain + camera().SetSceneExtent(nse::math::BoundingBox(Eigen::Vector3f(0, 0, 0), Eigen::Vector3f(PATCH_SIZE - 1, 0, PATCH_SIZE - 1))); + camera().FocusOnPoint(0.5f * Eigen::Vector3f(PATCH_SIZE - 1, 15, PATCH_SIZE - 1)); + camera().Zoom(-30); + camera().RotateAroundFocusPointLocal(Eigen::AngleAxisf(-0.5f, Eigen::Vector3f::UnitY()) * Eigen::AngleAxisf(-0.05f, Eigen::Vector3f::UnitX())); + camera().FixClippingPlanes(0.1, 1000); } -void Viewer::SetupGUI() +bool Viewer::resizeEvent(const Eigen::Vector2i&) { - auto mainWindow = SetupMainWindow(); + //Re-generate the texture and FBO for the background + glBindFramebuffer(GL_FRAMEBUFFER, backgroundFBO); + glBindTexture(GL_TEXTURE_2D, backgroundTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, backgroundTexture, 0); + auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (fboStatus != GL_FRAMEBUFFER_COMPLETE) + std::cout << "Warning: Background framebuffer is not complete: " << fboStatus << std::endl; + glBindFramebuffer(GL_FRAMEBUFFER, 0); - 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); - } - }); + return false; +} - auto primitiveBtn = new nanogui::PopupButton(mainWindow, "Create Primitive"); - primitiveBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); +void Viewer::LoadShaders() +{ + skyShader.init("Sky Shader", std::string((const char*)sky_vert, sky_vert_size), std::string((const char*)sky_frag, sky_frag_size)); + terrainShader.init("Terrain Shader", std::string((const char*)terrain_vert, terrain_vert_size), std::string((const char*)terrain_frag, terrain_frag_size)); +} - auto quadBtn = new nanogui::Button(primitiveBtn->popup(), "Quad"); - quadBtn->setCallback([this]() { CreateQuad(polymesh); MeshUpdated(true); }); +GLuint CreateTexture(const unsigned char* fileData, size_t fileLength, bool repeat = true) +{ + GLuint textureName; + int textureWidth, textureHeight, textureChannels; + auto pixelData = stbi_load_from_memory(fileData, fileLength, &textureWidth, &textureHeight, &textureChannels, 3); + textureName = 0; + stbi_image_free(pixelData); + return textureName; +} - auto diskBtn = new nanogui::Button(primitiveBtn->popup(), "Disk"); - diskBtn->setCallback([this]() { CreateDisk(polymesh, 1, 20); MeshUpdated(true); }); +void Viewer::CreateGeometry() +{ + //empty VAO for sky + emptyVAO.generate(); - auto tetBtn = new nanogui::Button(primitiveBtn->popup(), "Tetrahedron"); - tetBtn->setCallback([this]() { CreateTetrahedron(polymesh); MeshUpdated(true); }); + //terrain VAO + terrainVAO.generate(); + terrainVAO.bind(); + + std::vector positions; + std::vector indices; + + /*Generate positions and indices for a terrain patch with a + single triangle strip */ - 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); + terrainShader.bind(); + terrainPositions.uploadData(positions).bindToAttribute("position"); + terrainIndices.uploadData(indices.size() * sizeof(uint32_t), indices.data()); - 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(); + //textures + grassTexture = CreateTexture((unsigned char*)grass_jpg, grass_jpg_size); + rockTexture = CreateTexture((unsigned char*)rock_jpg, rock_jpg_size); + roadColorTexture = CreateTexture((unsigned char*)roadcolor_jpg, roadcolor_jpg_size); + roadNormalMap = CreateTexture((unsigned char*)roadnormals_jpg, roadnormals_jpg_size); + roadSpecularMap = CreateTexture((unsigned char*)roadspecular_jpg, roadspecular_jpg_size); + alphaMap = CreateTexture((unsigned char*)alpha_jpg, alpha_jpg_size, false); } -void Viewer::ColorMeshFromIds() +void Viewer::RenderSky() { - //Set face colors - for (auto f : polymesh.faces()) + Eigen::Matrix4f skyView = view; + for (int i = 0; i < 3; ++i) + skyView.col(i).normalize(); + skyView.col(3).head<3>().setZero(); + Eigen::Matrix4f skyMvp = proj * skyView; + glDepthMask(GL_FALSE); + glEnable(GL_DEPTH_CLAMP); + emptyVAO.bind(); + skyShader.bind(); + skyShader.setUniform("mvp", skyMvp); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); + glDisable(GL_DEPTH_CLAMP); + glDepthMask(GL_TRUE); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, backgroundFBO); + glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void CalculateViewFrustum(const Eigen::Matrix4f& mvp, Eigen::Vector4f* frustumPlanes, nse::math::BoundingBox& bbox) +{ + frustumPlanes[0] = (mvp.row(3) + mvp.row(0)).transpose(); + frustumPlanes[1] = (mvp.row(3) - mvp.row(0)).transpose(); + frustumPlanes[2] = (mvp.row(3) + mvp.row(1)).transpose(); + frustumPlanes[3] = (mvp.row(3) - mvp.row(1)).transpose(); + frustumPlanes[4] = (mvp.row(3) + mvp.row(2)).transpose(); + frustumPlanes[5] = (mvp.row(3) - mvp.row(2)).transpose(); + + Eigen::Matrix4f invMvp = mvp.inverse(); + bbox.reset(); + for(int x = -1; x <= 1; x += 2) + for(int y = -1; y <= 1; y += 2) + for (int z = -1; z <= 1; z += 2) { - 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); - } + Eigen::Vector4f corner = invMvp * Eigen::Vector4f(x, y, z, 1); + corner /= corner.w(); + bbox.expand(corner.head<3>()); } - hasColors = true; - MeshUpdated(); } -void Viewer::MeshUpdated(bool initNewMesh) +bool IsBoxCompletelyBehindPlane(const Eigen::Vector3f& boxMin, const Eigen::Vector3f& boxMax, const Eigen::Vector4f& plane) { - 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(); + return + plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMin.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMax.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMin.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMax.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0 && + plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0; } void Viewer::drawContents() { - glEnable(GL_DEPTH_TEST); - - Eigen::Matrix4f view, proj; camera().ComputeCameraMatrices(view, proj); - renderer.Render(view, proj, shadingBtn->selectedIndex() == 1); + Eigen::Matrix4f mvp = proj * view; + Eigen::Vector3f cameraPosition = view.inverse().col(3).head<3>(); + int visiblePatches = 0; + + RenderSky(); + + //render terrain + glEnable(GL_DEPTH_TEST); + terrainVAO.bind(); + terrainShader.bind(); + + terrainShader.setUniform("screenSize", Eigen::Vector2f(width(), height()), false); + terrainShader.setUniform("mvp", mvp); + terrainShader.setUniform("cameraPos", cameraPosition, false); + + /* Task: Render the terrain */ + + + //Render text + nvgBeginFrame(mNVGContext, width(), height(), mPixelRatio); + std::string text = "Patches visible: " + std::to_string(visiblePatches); + nvgText(mNVGContext, 10, 20, text.c_str(), nullptr); + nvgEndFrame(mNVGContext); } \ No newline at end of file diff --git a/exercise3/CMakeLists.txt b/exercise3/CMakeLists.txt index 4d0a509..f83db3f 100644 --- a/exercise3/CMakeLists.txt +++ b/exercise3/CMakeLists.txt @@ -3,14 +3,12 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/inc 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 - ) + 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(Exercise3 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise2/include/Primitives.h b/exercise3/include/Primitives.h similarity index 100% rename from exercise2/include/Primitives.h rename to exercise3/include/Primitives.h diff --git a/exercise2/include/ShellExtraction.h b/exercise3/include/ShellExtraction.h similarity index 100% rename from exercise2/include/ShellExtraction.h rename to exercise3/include/ShellExtraction.h diff --git a/exercise2/include/Smoothing.h b/exercise3/include/Smoothing.h similarity index 100% rename from exercise2/include/Smoothing.h rename to exercise3/include/Smoothing.h diff --git a/exercise2/include/Stripification.h b/exercise3/include/Stripification.h similarity index 100% rename from exercise2/include/Stripification.h rename to exercise3/include/Stripification.h diff --git a/exercise2/include/SurfaceArea.h b/exercise3/include/SurfaceArea.h similarity index 100% rename from exercise2/include/SurfaceArea.h rename to exercise3/include/SurfaceArea.h diff --git a/exercise3/include/Viewer.h b/exercise3/include/Viewer.h index fcda12f..29d694b 100644 --- a/exercise3/include/Viewer.h +++ b/exercise3/include/Viewer.h @@ -5,17 +5,8 @@ #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: @@ -24,67 +15,21 @@ public: void drawContents(); private: - - enum PrimitiveType - { - Vertex, Edge, Tri - }; - void SetupGUI(); - void MeshUpdated(); + void MeshUpdated(bool initNewMesh = false); - void FindClosestPoint(const Eigen::Vector3f& p); - void BuildGridVBO(); - void BuildRayVBOs(); + void ColorMeshFromIds(); - 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); + bool hasColors = false; nanogui::ComboBox* shadingBtn; - nanogui::CheckBox* chkRenderMesh; - nanogui::CheckBox* chkRenderGrid; - nanogui::CheckBox* chkRenderRay; - int raySteps; + unsigned int smoothingIterations; + nanogui::Slider* sldSmoothingStrength; + unsigned int stripificationTrials; - 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; + OpenMesh::FPropHandleT faceIdProperty; + OpenMesh::FPropHandleT faceColorProperty; }; diff --git a/exercise2/include/Volume.h b/exercise3/include/Volume.h similarity index 100% rename from exercise2/include/Volume.h rename to exercise3/include/Volume.h diff --git a/exercise2/include/sample_set.h b/exercise3/include/sample_set.h similarity index 100% rename from exercise2/include/sample_set.h rename to exercise3/include/sample_set.h diff --git a/exercise2/src/Primitives.cpp b/exercise3/src/Primitives.cpp similarity index 100% rename from exercise2/src/Primitives.cpp rename to exercise3/src/Primitives.cpp diff --git a/exercise2/src/ShellExtraction.cpp b/exercise3/src/ShellExtraction.cpp similarity index 100% rename from exercise2/src/ShellExtraction.cpp rename to exercise3/src/ShellExtraction.cpp diff --git a/exercise2/src/Smoothing.cpp b/exercise3/src/Smoothing.cpp similarity index 100% rename from exercise2/src/Smoothing.cpp rename to exercise3/src/Smoothing.cpp diff --git a/exercise2/src/Stripification.cpp b/exercise3/src/Stripification.cpp similarity index 100% rename from exercise2/src/Stripification.cpp rename to exercise3/src/Stripification.cpp diff --git a/exercise2/src/SurfaceArea.cpp b/exercise3/src/SurfaceArea.cpp similarity index 100% rename from exercise2/src/SurfaceArea.cpp rename to exercise3/src/SurfaceArea.cpp diff --git a/exercise3/src/Viewer.cpp b/exercise3/src/Viewer.cpp index e0f22e7..dd3b377 100644 --- a/exercise3/src/Viewer.cpp +++ b/exercise3/src/Viewer.cpp @@ -8,33 +8,49 @@ #include #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" -#include -#include "GridTraverser.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 3"), - renderer(polymesh), - closestPositions(nse::gui::VertexBuffer), - gridPositions(nse::gui::VertexBuffer), - rayPositions(nse::gui::VertexBuffer), rayCellsPositions(nse::gui::VertexBuffer) + : AbstractViewer("CG1 Exercise 2"), + renderer(polymesh) { SetupGUI(); - closestVAO.generate(); - gridVAO.generate(); - rayVAO.generate(); - rayCellsVAO.generate(); + polymesh.add_property(faceIdProperty); + polymesh.add_property(faceColorProperty); } void Viewer::SetupGUI() @@ -55,233 +71,185 @@ void Viewer::SetupGUI() "The specified file could not be loaded"); } else - MeshUpdated(); + MeshUpdated(true); } - }); - - 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); + 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::FindClosestPoint(const Eigen::Vector3f& p) +void Viewer::ColorMeshFromIds() { - if (polymesh.vertices_empty()) - return; - Eigen::Vector3f closest; - auto timeStart = std::chrono::high_resolution_clock::now(); - switch (cmbPrimitiveType->selectedIndex()) + //Set face colors + for (auto f : polymesh.faces()) { - case Vertex: - closest = vertexTree.ClosestPoint(p); - break; - case Edge: - closest = edgeTree.ClosestPoint(p); - break; - case Tri: - closest = triangleTree.ClosestPoint(p); - break; + 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); } - 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); + if (hasColors) + renderer.UpdateWithPerFaceColor(faceColorProperty); + else + renderer.Update(); } void Viewer::drawContents() { glEnable(GL_DEPTH_TEST); - if (!polymesh.vertices_empty()) - { - Eigen::Matrix4f view, proj; - camera().ComputeCameraMatrices(view, proj); - Eigen::Matrix4f mvp = proj * view; + Eigen::Matrix4f view, proj; + camera().ComputeCameraMatrices(view, proj); - 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(); - } - } + renderer.Render(view, proj, shadingBtn->selectedIndex() == 1); } \ No newline at end of file diff --git a/exercise2/src/Volume.cpp b/exercise3/src/Volume.cpp similarity index 100% rename from exercise2/src/Volume.cpp rename to exercise3/src/Volume.cpp diff --git a/exercise3/src/main.cpp b/exercise3/src/main.cpp index e94f898..e01821b 100644 --- a/exercise3/src/main.cpp +++ b/exercise3/src/main.cpp @@ -10,11 +10,9 @@ int main(int argc, char* argv[]) { - std::cout.imbue(std::locale("")); - nanogui::init(); - { + { nanogui::ref viewer = new Viewer(); viewer->setVisible(true); diff --git a/exercise4/CMakeLists.txt b/exercise4/CMakeLists.txt index dc3fd08..dc2f1b1 100644 --- a/exercise4/CMakeLists.txt +++ b/exercise4/CMakeLists.txt @@ -3,8 +3,14 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/inc 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 + 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(Exercise4 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise3/include/AABBTree.h b/exercise4/include/AABBTree.h similarity index 100% rename from exercise3/include/AABBTree.h rename to exercise4/include/AABBTree.h diff --git a/exercise3/include/Box.h b/exercise4/include/Box.h similarity index 100% rename from exercise3/include/Box.h rename to exercise4/include/Box.h diff --git a/exercise3/include/GridTraverser.h b/exercise4/include/GridTraverser.h similarity index 100% rename from exercise3/include/GridTraverser.h rename to exercise4/include/GridTraverser.h diff --git a/exercise3/include/GridUtils.h b/exercise4/include/GridUtils.h similarity index 100% rename from exercise3/include/GridUtils.h rename to exercise4/include/GridUtils.h diff --git a/exercise3/include/HashGrid.h b/exercise4/include/HashGrid.h similarity index 100% rename from exercise3/include/HashGrid.h rename to exercise4/include/HashGrid.h diff --git a/exercise3/include/LineSegment.h b/exercise4/include/LineSegment.h similarity index 100% rename from exercise3/include/LineSegment.h rename to exercise4/include/LineSegment.h diff --git a/exercise3/include/Point.h b/exercise4/include/Point.h similarity index 100% rename from exercise3/include/Point.h rename to exercise4/include/Point.h diff --git a/exercise3/include/Triangle.h b/exercise4/include/Triangle.h similarity index 100% rename from exercise3/include/Triangle.h rename to exercise4/include/Triangle.h diff --git a/exercise4/include/Viewer.h b/exercise4/include/Viewer.h index 00dcbad..fcda12f 100644 --- a/exercise4/include/Viewer.h +++ b/exercise4/include/Viewer.h @@ -8,8 +8,13 @@ #include #include -#include "Registration.h" -#include +#include "AABBTree.h" +#include "HashGrid.h" +#include "Point.h" +#include "LineSegment.h" +#include "Triangle.h" + +#include class Viewer : public nse::gui::AbstractViewer { @@ -18,32 +23,68 @@ public: void drawContents(); - virtual bool resizeEvent(const Eigen::Vector2i&); +private: -private: + enum PrimitiveType + { + Vertex, Edge, Tri + }; void SetupGUI(); - void MeshUpdated(); - void BuildCorrVBOs(); + void MeshUpdated(); - nse::math::BoundingBox meshBbox, expandedBbox; + 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* chkRenderTextureMap; - nanogui::CheckBox* chkRenderSecondMesh; + nanogui::CheckBox* chkRenderMesh; + nanogui::CheckBox* chkRenderGrid; + nanogui::CheckBox* chkRenderRay; + int raySteps; + + nse::gui::VectorInput* sldQuery, *sldRayOrigin, *sldRayDir; + nanogui::ComboBox* cmbPrimitiveType; HEMesh polymesh; - MeshRenderer renderer; + float bboxMaxLength; + MeshRenderer renderer; + + AABBTree vertexTree; + AABBTree edgeTree; + AABBTree triangleTree; + + HashGrid vertexGrid; + HashGrid edgeGrid; + HashGrid triangleGrid; - bool hasParametrization = false; - Eigen::Matrix4f texMapProjectionMatrix; + nse::gui::GLBuffer closestPositions; + nse::gui::GLVertexArray closestVAO; - Eigen::Affine3f secondMeshTransform; + nse::gui::GLBuffer gridPositions; + nse::gui::GLVertexArray gridVAO; + GLuint gridIndices; - std::mt19937 rnd; - std::vector correspondences; - - size_t corrCount; - nse::gui::GLBuffer corrPositions; - nse::gui::GLVertexArray corrVAO; + nse::gui::GLBuffer rayPositions, rayCellsPositions; + nse::gui::GLVertexArray rayVAO, rayCellsVAO; + GLuint rayCellsIndices; }; diff --git a/exercise3/src/AABBTree.cpp b/exercise4/src/AABBTree.cpp similarity index 100% rename from exercise3/src/AABBTree.cpp rename to exercise4/src/AABBTree.cpp diff --git a/exercise3/src/Box.cpp b/exercise4/src/Box.cpp similarity index 100% rename from exercise3/src/Box.cpp rename to exercise4/src/Box.cpp diff --git a/exercise3/src/GridTraverser.cpp b/exercise4/src/GridTraverser.cpp similarity index 100% rename from exercise3/src/GridTraverser.cpp rename to exercise4/src/GridTraverser.cpp diff --git a/exercise3/src/HashGrid.cpp b/exercise4/src/HashGrid.cpp similarity index 100% rename from exercise3/src/HashGrid.cpp rename to exercise4/src/HashGrid.cpp diff --git a/exercise3/src/LineSegment.cpp b/exercise4/src/LineSegment.cpp similarity index 100% rename from exercise3/src/LineSegment.cpp rename to exercise4/src/LineSegment.cpp diff --git a/exercise3/src/Point.cpp b/exercise4/src/Point.cpp similarity index 100% rename from exercise3/src/Point.cpp rename to exercise4/src/Point.cpp diff --git a/exercise3/src/Triangle.cpp b/exercise4/src/Triangle.cpp similarity index 100% rename from exercise3/src/Triangle.cpp rename to exercise4/src/Triangle.cpp diff --git a/exercise4/src/Viewer.cpp b/exercise4/src/Viewer.cpp index 92ba141..e0f22e7 100644 --- a/exercise4/src/Viewer.cpp +++ b/exercise4/src/Viewer.cpp @@ -13,23 +13,28 @@ #include +#include + #include -#include "Parametrization.h" -#include "Registration.h" +#include #include +#include "GridTraverser.h" Viewer::Viewer() - : AbstractViewer("CG1 Exercise 4"), + : AbstractViewer("CG1 Exercise 3"), renderer(polymesh), - corrPositions(nse::gui::VertexBuffer) + closestPositions(nse::gui::VertexBuffer), + gridPositions(nse::gui::VertexBuffer), + rayPositions(nse::gui::VertexBuffer), rayCellsPositions(nse::gui::VertexBuffer) { - polymesh.request_vertex_texcoords2D(); - - corrVAO.generate(); - SetupGUI(); + + closestVAO.generate(); + gridVAO.generate(); + rayVAO.generate(); + rayCellsVAO.generate(); } void Viewer::SetupGUI() @@ -52,184 +57,179 @@ void Viewer::SetupGUI() 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(); +} - 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)); +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; - 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(); + 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 - meshBbox.reset(); + nse::math::BoundingBox bbox; for (auto v : polymesh.vertices()) - meshBbox.expand(ToEigenVector(polymesh.point(v))); + bbox.expand(ToEigenVector(polymesh.point(v))); + camera().FocusOnBBox(bbox); + bboxMaxLength = bbox.diagonal().maxCoeff(); - 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(); + polymesh.triangulate(); - hasParametrization = false; + 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(); - BuildCorrVBOs(); } -void Viewer::BuildCorrVBOs() +void Viewer::BuildGridVBO() { - std::vector pos; - pos.reserve(correspondences.size() * 2); - for (auto& c : correspondences) + switch (cmbPrimitiveType->selectedIndex()) { - 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)); + 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); } - corrVAO.bind(); - ShaderPool::Instance()->simpleShader.bind(); - corrPositions.uploadData(pos).bindToAttribute("position"); - corrVAO.unbind(); + rayCellsVAO.bind(); + rayCellsPositions.uploadData(cellPositions).bindToAttribute("position"); + rayCellsVAO.unbind(); + rayCellsIndices = (GLuint)cellPositions.size(); } -bool Viewer::resizeEvent(const Eigen::Vector2i& size) +void Viewer::AddBoxVertices(const Box & box, std::vector& positions) { - AbstractViewer::resizeEvent(size); + 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); - 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; + 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() @@ -242,26 +242,46 @@ void Viewer::drawContents() camera().ComputeCameraMatrices(view, proj); Eigen::Matrix4f mvp = proj * view; - renderer.Render(view, proj, shadingBtn->selectedIndex() == 1, hasParametrization); + if(chkRenderMesh->checked()) + renderer.Render(view, proj, shadingBtn->selectedIndex() == 1); - if (chkRenderSecondMesh->checked()) + 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()) { - 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(); - } + gridVAO.bind(); + ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(0.2f, 0.2f, 0.2f, 1)); + glDrawArrays(GL_LINES, 0, gridIndices); + gridVAO.unbind(); } - if (hasParametrization && chkRenderTextureMap->checked()) + if (chkRenderRay->checked()) { - glDisable(GL_DEPTH_TEST); - renderer.RenderTextureMap(texMapProjectionMatrix, Eigen::Vector4f(1, 1, 1, 1)); + //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/exercise5/CMakeLists.txt b/exercise5/CMakeLists.txt new file mode 100644 index 0000000..c2e52b1 --- /dev/null +++ b/exercise5/CMakeLists.txt @@ -0,0 +1,10 @@ +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ) + +add_executable(Exercise5 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(Exercise5 CG1Common ${LIBS}) \ No newline at end of file diff --git a/exercise4/include/Parametrization.h b/exercise5/include/Parametrization.h similarity index 100% rename from exercise4/include/Parametrization.h rename to exercise5/include/Parametrization.h diff --git a/exercise4/include/Registration.h b/exercise5/include/Registration.h similarity index 100% rename from exercise4/include/Registration.h rename to exercise5/include/Registration.h diff --git a/exercise5/include/Viewer.h b/exercise5/include/Viewer.h new file mode 100644 index 0000000..00dcbad --- /dev/null +++ b/exercise5/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/exercise5/src/Parametrization.cpp similarity index 100% rename from exercise4/src/Parametrization.cpp rename to exercise5/src/Parametrization.cpp diff --git a/exercise4/src/Registration.cpp b/exercise5/src/Registration.cpp similarity index 100% rename from exercise4/src/Registration.cpp rename to exercise5/src/Registration.cpp diff --git a/exercise5/src/Viewer.cpp b/exercise5/src/Viewer.cpp new file mode 100644 index 0000000..92ba141 --- /dev/null +++ b/exercise5/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/exercise5/src/main.cpp b/exercise5/src/main.cpp new file mode 100644 index 0000000..e94f898 --- /dev/null +++ b/exercise5/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/solution/linux/Exercise2 b/solution/linux/Exercise2 index 969b19b..369df2c 100644 Binary files a/solution/linux/Exercise2 and b/solution/linux/Exercise2 differ diff --git a/solution/linux/Exercise3 b/solution/linux/Exercise3 index a0169c8..969b19b 100644 Binary files a/solution/linux/Exercise3 and b/solution/linux/Exercise3 differ diff --git a/solution/linux/Exercise4 b/solution/linux/Exercise4 index b278ca2..a0169c8 100644 Binary files a/solution/linux/Exercise4 and b/solution/linux/Exercise4 differ diff --git a/solution/linux/Exercise5 b/solution/linux/Exercise5 new file mode 100644 index 0000000..b278ca2 Binary files /dev/null and b/solution/linux/Exercise5 differ diff --git a/solution/win/Exercise2.exe b/solution/win/Exercise2.exe index 1c44ab9..03e057f 100644 Binary files a/solution/win/Exercise2.exe and b/solution/win/Exercise2.exe differ diff --git a/solution/win/Exercise3.exe b/solution/win/Exercise3.exe index 0e182f3..1c44ab9 100644 Binary files a/solution/win/Exercise3.exe and b/solution/win/Exercise3.exe differ diff --git a/solution/win/Exercise4.exe b/solution/win/Exercise4.exe index 4c4c4b1..0e182f3 100644 Binary files a/solution/win/Exercise4.exe and b/solution/win/Exercise4.exe differ diff --git a/solution/win/Exercise5.exe b/solution/win/Exercise5.exe new file mode 100644 index 0000000..4c4c4b1 Binary files /dev/null and b/solution/win/Exercise5.exe differ