This commit is contained in:
hodasemi 2018-11-16 08:40:48 +01:00
commit 166e697630
68 changed files with 1197 additions and 731 deletions

View file

@ -48,3 +48,4 @@ add_subdirectory(exercise1)
add_subdirectory(exercise2) add_subdirectory(exercise2)
add_subdirectory(exercise3) add_subdirectory(exercise3)
add_subdirectory(exercise4) add_subdirectory(exercise4)
add_subdirectory(exercise5)

View file

@ -33,7 +33,7 @@ namespace nse {
Eigen::Matrix4f &proj, Eigen::Matrix4f &proj,
float customAspectRatio = 0) const; 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); void Zoom(float amount);
//Sets the extent of the scene, which is kept between znear/zfar. //Sets the extent of the scene, which is kept between znear/zfar.
@ -64,6 +64,8 @@ namespace nse {
//Forwarded resize event. //Forwarded resize event.
void resize(const Eigen::Vector2i & s); void resize(const Eigen::Vector2i & s);
void FixClippingPlanes(float znear, float zfar);
//Returns the point that the camera focuses on //Returns the point that the camera focuses on
const Eigen::Vector3f& GetFocusPoint() const; const Eigen::Vector3f& GetFocusPoint() const;
@ -99,6 +101,8 @@ namespace nse {
Eigen::Vector3f modelTranslation_start = Eigen::Vector3f::Zero(); Eigen::Vector3f modelTranslation_start = Eigen::Vector3f::Zero();
Eigen::Vector2i translation_start; //mouse position on the screen where translation started Eigen::Vector2i translation_start; //mouse position on the screen where translation started
float fixedZNear, fixedZFar;
}; };
} }
} }

View file

@ -14,7 +14,7 @@
using namespace nse::gui; using namespace nse::gui;
Camera::Camera(const nanogui::Widget & parent) Camera::Camera(const nanogui::Widget & parent)
: parent(parent) : parent(parent), fixedZNear(std::numeric_limits<float>::quiet_NaN())
{ {
params.arcball.setSize(parent.size()); 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 depthOfSceneCenter = (params.sceneCenter - cameraPosition).dot(viewDirection);
float minZNear = 0.001f * params.sceneRadius; float minZNear = 0.001f * params.sceneRadius;
float znear = std::max(minZNear, depthOfSceneCenter - params.sceneRadius); float znear, zfar;
float zfar = std::max(znear + minZNear, depthOfSceneCenter + params.sceneRadius); 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 fH = std::tan(params.fovy / 360.0f * (float)M_PI) * znear;
float aspectRatio = customAspectRatio == 0 ? (float)parent.width() / parent.height() : customAspectRatio; 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<float, 3>& bbox) void Camera::SetSceneExtent(const nse::math::BoundingBox<float, 3>& bbox)
{ {
fixedZNear = std::numeric_limits<float>::quiet_NaN();
params.sceneCenter = 0.5f * (bbox.min + bbox.max); params.sceneCenter = 0.5f * (bbox.min + bbox.max);
params.sceneRadius = bbox.diagonal().norm() / 2.0f; params.sceneRadius = bbox.diagonal().norm() / 2.0f;
} }
@ -175,3 +185,9 @@ void Camera::resize(const Eigen::Vector2i & s)
{ {
params.arcball.setSize(s); params.arcball.setSize(s);
} }
void Camera::FixClippingPlanes(float znear, float zfar)
{
fixedZNear = znear;
fixedZFar = zfar;
}

View file

@ -132,5 +132,8 @@ void nse::util::GLDebug::SetupDebugCallback()
{ {
glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 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);
} }

View file

@ -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 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include )
add_executable(Exercise2 MACOSX_BUNDLE add_executable(Exercise2 MACOSX_BUNDLE
glsl.cpp
textures.cpp
src/main.cpp src/main.cpp
src/Viewer.cpp include/Viewer.h src/Viewer.cpp include/Viewer.h
src/Primitives.cpp include/Primitives.h ${GLSL_FILES})
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}) target_link_libraries(Exercise2 CG1Common ${LIBS})

21
exercise2/glsl/sky.frag Normal file
View file

@ -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));
}

19
exercise2/glsl/sky.vert Normal file
View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -5,31 +5,38 @@
#pragma once #pragma once
#include <gui/AbstractViewer.h> #include <gui/AbstractViewer.h>
#include <util/OpenMeshUtils.h> #include <gui/GLShader.h>
#include <gui/GLBuffer.h>
#include <gui/GLVertexArray.h>
class Viewer : public nse::gui::AbstractViewer class Viewer : public nse::gui::AbstractViewer
{ {
public: public:
Viewer(); Viewer();
void LoadShaders();
void CreateGeometry();
void drawContents(); void drawContents();
bool resizeEvent(const Eigen::Vector2i&);
private: private:
void SetupGUI();
void MeshUpdated(bool initNewMesh = false);
void ColorMeshFromIds(); void RenderSky();
bool hasColors = false; Eigen::Matrix4f view, proj;
nanogui::ComboBox* shadingBtn; nse::gui::GLShader skyShader;
unsigned int smoothingIterations; nse::gui::GLVertexArray emptyVAO;
nanogui::Slider* sldSmoothingStrength;
unsigned int stripificationTrials;
HEMesh polymesh; nse::gui::GLShader terrainShader;
MeshRenderer renderer; nse::gui::GLVertexArray terrainVAO;
nse::gui::GLBuffer terrainPositions;
nse::gui::GLBuffer terrainIndices;
OpenMesh::FPropHandleT<int> faceIdProperty; GLuint grassTexture, rockTexture, roadColorTexture, roadNormalMap, roadSpecularMap, alphaMap;
OpenMesh::FPropHandleT<Eigen::Vector4f> faceColorProperty;
nse::gui::GLBuffer offsetBuffer;
GLuint backgroundFBO, backgroundTexture;
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -5,251 +5,182 @@
#include "Viewer.h" #include "Viewer.h"
#include <nanogui/window.h> #include <nanogui/window.h>
#include <nanogui/button.h>
#include <nanogui/checkbox.h>
#include <nanogui/messagedialog.h>
#include <nanogui/popupbutton.h>
#include <nanogui/layout.h> #include <nanogui/layout.h>
#include <nanogui/combobox.h> #include <nanogui/checkbox.h>
#include <iostream>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <gui/SliderHelper.h> #include <gui/SliderHelper.h>
#include "Primitives.h" #include <iostream>
#include "SurfaceArea.h"
#include "Volume.h"
#include "ShellExtraction.h"
#include "Smoothing.h"
#include "Stripification.h"
const int segmentColorCount = 12; #include <stb_image.h>
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 "glsl.h"
#include "textures.h"
const uint32_t PATCH_SIZE = 256; //number of vertices along one side of the terrain patch
Viewer::Viewer() Viewer::Viewer()
: AbstractViewer("CG1 Exercise 2"), : AbstractViewer("CG1 Exercise 2"),
renderer(polymesh) terrainPositions(nse::gui::VertexBuffer), terrainIndices(nse::gui::IndexBuffer),
offsetBuffer(nse::gui::VertexBuffer)
{ {
SetupGUI(); LoadShaders();
CreateGeometry();
polymesh.add_property(faceIdProperty); //Create a texture and framebuffer for the background
polymesh.add_property(faceColorProperty); glGenFramebuffers(1, &backgroundFBO);
glGenTextures(1, &backgroundTexture);
//Align camera to view a reasonable part of the terrain
camera().SetSceneExtent(nse::math::BoundingBox<float, 3>(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"); return false;
loadFileBtn->setCallback([this]() {
std::vector<std::pair<std::string, std::string>> 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() void Viewer::LoadShaders()
{ {
//Set face colors skyShader.init("Sky Shader", std::string((const char*)sky_vert, sky_vert_size), std::string((const char*)sky_frag, sky_frag_size));
for (auto f : polymesh.faces()) terrainShader.init("Terrain Shader", std::string((const char*)terrain_vert, terrain_vert_size), std::string((const char*)terrain_frag, terrain_frag_size));
}
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;
}
void Viewer::CreateGeometry()
{
//empty VAO for sky
emptyVAO.generate();
//terrain VAO
terrainVAO.generate();
terrainVAO.bind();
std::vector<Eigen::Vector4f> positions;
std::vector<uint32_t> indices;
/*Generate positions and indices for a terrain patch with a
single triangle strip */
terrainShader.bind();
terrainPositions.uploadData(positions).bindToAttribute("position");
terrainIndices.uploadData(indices.size() * sizeof(uint32_t), indices.data());
//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::RenderSky()
{
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<float, 3>& 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); Eigen::Vector4f corner = invMvp * Eigen::Vector4f(x, y, z, 1);
if (shell < 0) corner /= corner.w();
polymesh.property(faceColorProperty, f) = Eigen::Vector4f(0, 0, 0, 1); bbox.expand(corner.head<3>());
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) bool IsBoxCompletelyBehindPlane(const Eigen::Vector3f& boxMin, const Eigen::Vector3f& boxMax, const Eigen::Vector4f& plane)
{ {
if (initNewMesh) return
{ plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMin.z(), 1)) < 0 &&
hasColors = false; plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMax.z(), 1)) < 0 &&
plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
//calculate the bounding box of the mesh plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
nse::math::BoundingBox<float, 3> bbox; plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMin.z(), 1)) < 0 &&
for (auto v : polymesh.vertices()) plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMax.z(), 1)) < 0 &&
bbox.expand(ToEigenVector(polymesh.point(v))); plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0 &&
camera().FocusOnBBox(bbox); plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0;
}
if (hasColors)
renderer.UpdateWithPerFaceColor(faceColorProperty);
else
renderer.Update();
} }
void Viewer::drawContents() void Viewer::drawContents()
{ {
glEnable(GL_DEPTH_TEST);
Eigen::Matrix4f view, proj;
camera().ComputeCameraMatrices(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);
} }

View file

@ -3,14 +3,12 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/inc
add_executable(Exercise3 MACOSX_BUNDLE add_executable(Exercise3 MACOSX_BUNDLE
src/main.cpp src/main.cpp
src/Viewer.cpp include/Viewer.h src/Viewer.cpp include/Viewer.h
src/AABBTree.cpp include/AABBTree.h src/Primitives.cpp include/Primitives.h
src/Box.cpp include/Box.h src/SurfaceArea.cpp include/SurfaceArea.h
src/LineSegment.cpp include/LineSegment.h src/Volume.cpp include/Volume.h
src/Point.cpp include/Point.h src/ShellExtraction.cpp include/ShellExtraction.h
src/Triangle.cpp include/Triangle.h src/Smoothing.cpp include/Smoothing.h
include/GridUtils.h src/Stripification.cpp include/Stripification.h
src/HashGrid.cpp include/HashGrid.h include/sample_set.h)
src/GridTraverser.cpp include/GridTraverser.h
)
target_link_libraries(Exercise3 CG1Common ${LIBS}) target_link_libraries(Exercise3 CG1Common ${LIBS})

View file

@ -5,17 +5,8 @@
#pragma once #pragma once
#include <gui/AbstractViewer.h> #include <gui/AbstractViewer.h>
#include <gui/SliderHelper.h>
#include <util/OpenMeshUtils.h> #include <util/OpenMeshUtils.h>
#include "AABBTree.h"
#include "HashGrid.h"
#include "Point.h"
#include "LineSegment.h"
#include "Triangle.h"
#include <gui/ShaderPool.h>
class Viewer : public nse::gui::AbstractViewer class Viewer : public nse::gui::AbstractViewer
{ {
public: public:
@ -24,67 +15,21 @@ public:
void drawContents(); void drawContents();
private: private:
enum PrimitiveType
{
Vertex, Edge, Tri
};
void SetupGUI(); void SetupGUI();
void MeshUpdated(); void MeshUpdated(bool initNewMesh = false);
void FindClosestPoint(const Eigen::Vector3f& p); void ColorMeshFromIds();
void BuildGridVBO();
void BuildRayVBOs();
template <typename Grid> bool hasColors = false;
void BuildGridVBO(const Grid& grid)
{
std::vector<Eigen::Vector4f> 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<Eigen::Vector4f>& positions);
nanogui::ComboBox* shadingBtn; nanogui::ComboBox* shadingBtn;
nanogui::CheckBox* chkRenderMesh; unsigned int smoothingIterations;
nanogui::CheckBox* chkRenderGrid; nanogui::Slider* sldSmoothingStrength;
nanogui::CheckBox* chkRenderRay; unsigned int stripificationTrials;
int raySteps;
nse::gui::VectorInput* sldQuery, *sldRayOrigin, *sldRayDir;
nanogui::ComboBox* cmbPrimitiveType;
HEMesh polymesh; HEMesh polymesh;
float bboxMaxLength;
MeshRenderer renderer; MeshRenderer renderer;
AABBTree<Point> vertexTree; OpenMesh::FPropHandleT<int> faceIdProperty;
AABBTree<LineSegment> edgeTree; OpenMesh::FPropHandleT<Eigen::Vector4f> faceColorProperty;
AABBTree<Triangle> triangleTree;
HashGrid<Point> vertexGrid;
HashGrid<LineSegment> edgeGrid;
HashGrid<Triangle> 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;
}; };

View file

@ -8,33 +8,49 @@
#include <nanogui/button.h> #include <nanogui/button.h>
#include <nanogui/checkbox.h> #include <nanogui/checkbox.h>
#include <nanogui/messagedialog.h> #include <nanogui/messagedialog.h>
#include <nanogui/popupbutton.h>
#include <nanogui/layout.h> #include <nanogui/layout.h>
#include <nanogui/combobox.h> #include <nanogui/combobox.h>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <iostream> #include <iostream>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <gui/SliderHelper.h> #include <gui/SliderHelper.h>
#include <chrono> #include "Primitives.h"
#include "SurfaceArea.h"
#include "Volume.h"
#include "ShellExtraction.h"
#include "Smoothing.h"
#include "Stripification.h"
#include <gui/ShaderPool.h> const int segmentColorCount = 12;
#include "GridTraverser.h" 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() Viewer::Viewer()
: AbstractViewer("CG1 Exercise 3"), : AbstractViewer("CG1 Exercise 2"),
renderer(polymesh), renderer(polymesh)
closestPositions(nse::gui::VertexBuffer),
gridPositions(nse::gui::VertexBuffer),
rayPositions(nse::gui::VertexBuffer), rayCellsPositions(nse::gui::VertexBuffer)
{ {
SetupGUI(); SetupGUI();
closestVAO.generate(); polymesh.add_property(faceIdProperty);
gridVAO.generate(); polymesh.add_property(faceColorProperty);
rayVAO.generate();
rayCellsVAO.generate();
} }
void Viewer::SetupGUI() void Viewer::SetupGUI()
@ -55,233 +71,185 @@ void Viewer::SetupGUI()
"The specified file could not be loaded"); "The specified file could not be loaded");
} }
else else
MeshUpdated(); MeshUpdated(true);
} }
}); });
cmbPrimitiveType = new nanogui::ComboBox(mainWindow, { "Use Vertices", "Use Edges", "Use Triangles" }); auto primitiveBtn = new nanogui::PopupButton(mainWindow, "Create Primitive");
cmbPrimitiveType->setCallback([this](int) { FindClosestPoint(sldQuery->Value()); BuildGridVBO(); }); primitiveBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4));
sldQuery = new nse::gui::VectorInput(mainWindow, "Query", Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), [this](const Eigen::Vector3f& p) { FindClosestPoint(p); }); auto quadBtn = new nanogui::Button(primitiveBtn->popup(), "Quad");
quadBtn->setCallback([this]() { CreateQuad(polymesh); MeshUpdated(true); });
sldRayOrigin = new nse::gui::VectorInput(mainWindow, "Ray Origin", Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), [this](const Eigen::Vector3f& p) { BuildRayVBOs(); }); auto diskBtn = new nanogui::Button(primitiveBtn->popup(), "Disk");
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(); }); diskBtn->setCallback([this]() { CreateDisk(polymesh, 1, 20); MeshUpdated(true); });
nanogui::TextBox* txtRaySteps;
auto sldRaySteps = nse::gui::AddLabeledSlider(mainWindow, "Ray Steps", std::make_pair(1, 200), 80, txtRaySteps); auto tetBtn = new nanogui::Button(primitiveBtn->popup(), "Tetrahedron");
sldRaySteps->setCallback([this, txtRaySteps](float value) { tetBtn->setCallback([this]() { CreateTetrahedron(polymesh); MeshUpdated(true); });
raySteps = (int)std::round(value);
txtRaySteps->setValue(std::to_string(raySteps)); auto octaBtn = new nanogui::Button(primitiveBtn->popup(), "Octahedron");
BuildRayVBOs(); 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());
}); });
sldRaySteps->callback()(sldRaySteps->value());
chkRenderMesh = new nanogui::CheckBox(mainWindow, "Render Mesh"); chkRenderMesh->setChecked(true); auto calcVolBtn = new nanogui::Button(mainWindow, "Calculate Mesh Volume");
chkRenderGrid = new nanogui::CheckBox(mainWindow, "Render Non-Empty Grid Cells"); chkRenderGrid->setChecked(false); calcVolBtn->setCallback([this]() {
chkRenderRay = new nanogui::CheckBox(mainWindow, "Render Ray"); chkRenderRay->setChecked(false); //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" }); shadingBtn = new nanogui::ComboBox(mainWindow, { "Smooth Shading", "Flat Shading" });
performLayout(); performLayout();
} }
void Viewer::FindClosestPoint(const Eigen::Vector3f& p) void Viewer::ColorMeshFromIds()
{ {
if (polymesh.vertices_empty()) //Set face colors
return; for (auto f : polymesh.faces())
Eigen::Vector3f closest;
auto timeStart = std::chrono::high_resolution_clock::now();
switch (cmbPrimitiveType->selectedIndex())
{ {
case Vertex: auto shell = polymesh.property(faceIdProperty, f);
closest = vertexTree.ClosestPoint(p); if (shell < 0)
break; polymesh.property(faceColorProperty, f) = Eigen::Vector4f(0, 0, 0, 1);
case Edge: else
closest = edgeTree.ClosestPoint(p); {
break; auto& color = segmentColors[shell % segmentColorCount];
case Tri: polymesh.property(faceColorProperty, f) = Eigen::Vector4f(color[0], color[1], color[2], 1);
closest = triangleTree.ClosestPoint(p); }
break;
} }
auto timeEnd = std::chrono::high_resolution_clock::now(); hasColors = true;
std::cout << std::fixed << "Closest point query took " << std::chrono::duration_cast<std::chrono::microseconds>(timeEnd - timeStart).count() << " microseconds." << std::endl; MeshUpdated();
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() void Viewer::MeshUpdated(bool initNewMesh)
{ {
//calculate the bounding Box of the mesh if (initNewMesh)
nse::math::BoundingBox<float, 3> 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: hasColors = false;
BuildGridVBO(vertexGrid);
break;
case Edge:
BuildGridVBO(edgeGrid);
break;
case Tri:
BuildGridVBO(triangleGrid);
break;
}
}
void Viewer::BuildRayVBOs() //calculate the bounding box of the mesh
{ nse::math::BoundingBox<float, 3> bbox;
if (polymesh.vertices_empty()) for (auto v : polymesh.vertices())
return; bbox.expand(ToEigenVector(polymesh.point(v)));
camera().FocusOnBBox(bbox);
//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<Eigen::Vector4f> 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(); if (hasColors)
rayCellsPositions.uploadData(cellPositions).bindToAttribute("position"); renderer.UpdateWithPerFaceColor(faceColorProperty);
rayCellsVAO.unbind(); else
rayCellsIndices = (GLuint)cellPositions.size(); renderer.Update();
}
void Viewer::AddBoxVertices(const Box & box, std::vector<Eigen::Vector4f>& 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() void Viewer::drawContents()
{ {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
if (!polymesh.vertices_empty()) Eigen::Matrix4f view, proj;
{ camera().ComputeCameraMatrices(view, proj);
Eigen::Matrix4f view, proj;
camera().ComputeCameraMatrices(view, proj);
Eigen::Matrix4f mvp = proj * view;
if(chkRenderMesh->checked()) renderer.Render(view, proj, shadingBtn->selectedIndex() == 1);
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();
}
}
} }

View file

@ -10,8 +10,6 @@
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
std::cout.imbue(std::locale(""));
nanogui::init(); nanogui::init();
{ {

View file

@ -3,8 +3,14 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/inc
add_executable(Exercise4 MACOSX_BUNDLE add_executable(Exercise4 MACOSX_BUNDLE
src/main.cpp src/main.cpp
src/Viewer.cpp include/Viewer.h src/Viewer.cpp include/Viewer.h
src/Parametrization.cpp include/Parametrization.h src/AABBTree.cpp include/AABBTree.h
src/Registration.cpp include/Registration.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}) target_link_libraries(Exercise4 CG1Common ${LIBS})

View file

@ -8,8 +8,13 @@
#include <gui/SliderHelper.h> #include <gui/SliderHelper.h>
#include <util/OpenMeshUtils.h> #include <util/OpenMeshUtils.h>
#include "Registration.h" #include "AABBTree.h"
#include <random> #include "HashGrid.h"
#include "Point.h"
#include "LineSegment.h"
#include "Triangle.h"
#include <gui/ShaderPool.h>
class Viewer : public nse::gui::AbstractViewer class Viewer : public nse::gui::AbstractViewer
{ {
@ -18,32 +23,68 @@ public:
void drawContents(); void drawContents();
virtual bool resizeEvent(const Eigen::Vector2i&);
private: private:
enum PrimitiveType
{
Vertex, Edge, Tri
};
void SetupGUI(); void SetupGUI();
void MeshUpdated(); void MeshUpdated();
void BuildCorrVBOs();
nse::math::BoundingBox<float, 3> meshBbox, expandedBbox; void FindClosestPoint(const Eigen::Vector3f& p);
void BuildGridVBO();
void BuildRayVBOs();
template <typename Grid>
void BuildGridVBO(const Grid& grid)
{
std::vector<Eigen::Vector4f> 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<Eigen::Vector4f>& positions);
nanogui::ComboBox* shadingBtn; nanogui::ComboBox* shadingBtn;
nanogui::CheckBox* chkRenderTextureMap; nanogui::CheckBox* chkRenderMesh;
nanogui::CheckBox* chkRenderSecondMesh; nanogui::CheckBox* chkRenderGrid;
nanogui::CheckBox* chkRenderRay;
int raySteps;
nse::gui::VectorInput* sldQuery, *sldRayOrigin, *sldRayDir;
nanogui::ComboBox* cmbPrimitiveType;
HEMesh polymesh; HEMesh polymesh;
float bboxMaxLength;
MeshRenderer renderer; MeshRenderer renderer;
bool hasParametrization = false; AABBTree<Point> vertexTree;
Eigen::Matrix4f texMapProjectionMatrix; AABBTree<LineSegment> edgeTree;
AABBTree<Triangle> triangleTree;
Eigen::Affine3f secondMeshTransform; HashGrid<Point> vertexGrid;
HashGrid<LineSegment> edgeGrid;
HashGrid<Triangle> triangleGrid;
std::mt19937 rnd; nse::gui::GLBuffer closestPositions;
std::vector<correspondence> correspondences; nse::gui::GLVertexArray closestVAO;
size_t corrCount; nse::gui::GLBuffer gridPositions;
nse::gui::GLBuffer corrPositions; nse::gui::GLVertexArray gridVAO;
nse::gui::GLVertexArray corrVAO; GLuint gridIndices;
nse::gui::GLBuffer rayPositions, rayCellsPositions;
nse::gui::GLVertexArray rayVAO, rayCellsVAO;
GLuint rayCellsIndices;
}; };

View file

@ -13,23 +13,28 @@
#include <OpenMesh/Core/IO/MeshIO.hh> #include <OpenMesh/Core/IO/MeshIO.hh>
#include <iostream>
#include <gui/SliderHelper.h> #include <gui/SliderHelper.h>
#include "Parametrization.h" #include <chrono>
#include "Registration.h"
#include <gui/ShaderPool.h> #include <gui/ShaderPool.h>
#include "GridTraverser.h"
Viewer::Viewer() Viewer::Viewer()
: AbstractViewer("CG1 Exercise 4"), : AbstractViewer("CG1 Exercise 3"),
renderer(polymesh), 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(); SetupGUI();
closestVAO.generate();
gridVAO.generate();
rayVAO.generate();
rayCellsVAO.generate();
} }
void Viewer::SetupGUI() void Viewer::SetupGUI()
@ -54,182 +59,177 @@ void Viewer::SetupGUI()
} }
}); });
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" }); shadingBtn = new nanogui::ComboBox(mainWindow, { "Smooth Shading", "Flat Shading" });
performLayout(); performLayout();
}
auto paramWindow = new nanogui::Window(this, "Parametrization"); void Viewer::FindClosestPoint(const Eigen::Vector3f& p)
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)); 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<std::chrono::microseconds>(timeEnd - timeStart).count() << " microseconds." << std::endl;
auto cmbWeightType = new nanogui::ComboBox(paramWindow, { "Constant Weight", "Edge Length Weight", "Inverse Edge Length Weight", "Cotan Weight" }); Eigen::Matrix4Xf points(4, 2);
points.block<3, 1>(0, 0) = p;
points.block<3, 1>(0, 1) = closest;
points.row(3).setConstant(1);
auto parametrizeBtn = new nanogui::Button(paramWindow, "Calculate Parametrization"); closestVAO.bind();
parametrizeBtn->setCallback([this, cmbWeightType]() { ShaderPool::Instance()->simpleShader.bind();
if (polymesh.n_vertices() == 0) closestPositions.uploadData(points).bindToAttribute("position");
{ closestVAO.unbind();
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<CONSTANT_WEIGHT>(polymesh);
break;
case 1:
success = ComputeParametrizationOfTopologicalDisk<EDGE_LENGTH_WEIGHT>(polymesh);
break;
case 2:
success = ComputeParametrizationOfTopologicalDisk<INV_EDGE_LENGTH_WEIGHT>(polymesh);
break;
case 3:
success = ComputeParametrizationOfTopologicalDisk<COTAN_WEIGHT>(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<float> 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<int> 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<float> 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() void Viewer::MeshUpdated()
{ {
//calculate the bounding Box of the mesh //calculate the bounding Box of the mesh
meshBbox.reset(); nse::math::BoundingBox<float, 3> bbox;
for (auto v : polymesh.vertices()) for (auto v : polymesh.vertices())
meshBbox.expand(ToEigenVector(polymesh.point(v))); bbox.expand(ToEigenVector(polymesh.point(v)));
camera().FocusOnBBox(bbox);
expandedBbox = meshBbox; bboxMaxLength = bbox.diagonal().maxCoeff();
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(); polymesh.triangulate();
correspondences.clear();
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(); renderer.Update();
BuildCorrVBOs();
} }
void Viewer::BuildCorrVBOs() void Viewer::BuildGridVBO()
{ {
std::vector<Eigen::Vector4f> pos; switch (cmbPrimitiveType->selectedIndex())
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)); case Vertex:
pos.push_back(Eigen::Vector4f(c.second.x(), c.second.y(), c.second.z(), 1.0f)); 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<Eigen::Vector4f> 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(); rayCellsVAO.bind();
ShaderPool::Instance()->simpleShader.bind(); rayCellsPositions.uploadData(cellPositions).bindToAttribute("position");
corrPositions.uploadData(pos).bindToAttribute("position"); rayCellsVAO.unbind();
corrVAO.unbind(); rayCellsIndices = (GLuint)cellPositions.size();
} }
bool Viewer::resizeEvent(const Eigen::Vector2i& size) void Viewer::AddBoxVertices(const Box & box, std::vector<Eigen::Vector4f>& 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(); positions.push_back(o + z);
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); positions.push_back(o + z + x);
texMapProjectionMatrix = proj.matrix(); 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);
return true; 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() void Viewer::drawContents()
@ -242,26 +242,46 @@ void Viewer::drawContents()
camera().ComputeCameraMatrices(view, proj); camera().ComputeCameraMatrices(view, proj);
Eigen::Matrix4f mvp = proj * view; 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)); gridVAO.bind();
if (correspondences.size() > 0) ShaderPool::Instance()->simpleShader.setUniform("color", Eigen::Vector4f(0.2f, 0.2f, 0.2f, 1));
{ glDrawArrays(GL_LINES, 0, gridIndices);
corrVAO.bind(); gridVAO.unbind();
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()) if (chkRenderRay->checked())
{ {
glDisable(GL_DEPTH_TEST); //Draw line for ray
renderer.RenderTextureMap(texMapProjectionMatrix, Eigen::Vector4f(1, 1, 1, 1)); 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();
} }
} }
} }

10
exercise5/CMakeLists.txt Normal file
View file

@ -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})

View file

@ -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 <gui/AbstractViewer.h>
#include <gui/SliderHelper.h>
#include <util/OpenMeshUtils.h>
#include "Registration.h"
#include <random>
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<float, 3> 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<correspondence> correspondences;
size_t corrCount;
nse::gui::GLBuffer corrPositions;
nse::gui::GLVertexArray corrVAO;
};

267
exercise5/src/Viewer.cpp Normal file
View file

@ -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 <nanogui/window.h>
#include <nanogui/button.h>
#include <nanogui/checkbox.h>
#include <nanogui/messagedialog.h>
#include <nanogui/layout.h>
#include <nanogui/combobox.h>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <gui/SliderHelper.h>
#include "Parametrization.h"
#include "Registration.h"
#include <gui/ShaderPool.h>
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<std::pair<std::string, std::string>> 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<CONSTANT_WEIGHT>(polymesh);
break;
case 1:
success = ComputeParametrizationOfTopologicalDisk<EDGE_LENGTH_WEIGHT>(polymesh);
break;
case 2:
success = ComputeParametrizationOfTopologicalDisk<INV_EDGE_LENGTH_WEIGHT>(polymesh);
break;
case 3:
success = ComputeParametrizationOfTopologicalDisk<COTAN_WEIGHT>(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<float> 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<int> 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<float> 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<Eigen::Vector4f> 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));
}
}
}

38
exercise5/src/main.cpp Normal file
View file

@ -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 <iostream>
#include <util/GLDebug.h>
#include "Viewer.h"
int main(int argc, char* argv[])
{
std::cout.imbue(std::locale(""));
nanogui::init();
{
nanogui::ref<Viewer> 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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
solution/linux/Exercise5 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
solution/win/Exercise5.exe Normal file

Binary file not shown.