#include "gui/Viewer.h" #include "gui/ShaderPool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stb_image_write.h" #define USE_MULTISAMPLING //#define LAPTOP_COMPAT Viewer::Viewer() : AbstractViewer("Generalized Motorcycle Graph", 1280, 800, #ifdef USE_MULTISAMPLING 4 #else 1 #endif ), meshVertices(nse::gui::VertexBuffer), meshIndices(nse::gui::IndexBuffer), singularityPositions(nse::gui::VertexBuffer), singularityColors(nse::gui::VertexBuffer), singularityCount(0), wireframeIndices(nse::gui::IndexBuffer), fencedRegionIndices(nse::gui::IndexBuffer), fencedRegionFaceIndices(nse::gui::IndexBuffer), motorcycleIndices(nse::gui::IndexBuffer), motorcycleIndexCount(0), patchPositionsVBO(nse::gui::VertexBuffer), patchColorsVBO(nse::gui::VertexBuffer), patchTexCoordsVBO(nse::gui::VertexBuffer), patchIndexCount(0), selectedRegion(-1), brokenArcsIndices(nse::gui::IndexBuffer) { ShaderPool::Instance()->CompileAll(); SetupGUI(); glGenFramebuffers(1, &fbo); glGenFramebuffers(1, &fboSinglePixel); glGenTextures(1, &colorTexture); glGenTextures(1, &pickingTexture); glGenTextures(1, &pickingTextureSinglePixel); glGenRenderbuffers(1, &depthBuffer); emptyVAO.generate(); for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = 0; fencedRegionFaceIndexCount[i] = 0; } } bool Viewer::resizeEvent(const Eigen::Vector2i& size) { AbstractViewer::resizeEvent(size); GLenum status; #ifndef LAPTOP_COMPAT glBindFramebuffer(GL_FRAMEBUFFER, fbo); auto textureTarget = (nSamples == 1 ? GL_TEXTURE_2D : GL_TEXTURE_2D_MULTISAMPLE); glBindTexture(textureTarget, colorTexture); if (nSamples == 1) glTexImage2D(textureTarget, 0, GL_RGBA8, width(), height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); else glTexImage2DMultisample(textureTarget, nSamples, GL_RGBA8, width(), height(), true); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureTarget, colorTexture, 0); glBindTexture(textureTarget, pickingTexture); if (nSamples == 1) glTexImage2D(textureTarget, 0, GL_R32I, width(), height(), 0, GL_RED_INTEGER, GL_INT, nullptr); else glTexImage2DMultisample(textureTarget, nSamples, GL_R32I, width(), height(), true); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, textureTarget, pickingTexture, 0); glBindTexture(textureTarget, 0); glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); if (nSamples == 1) glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width(), height()); else glRenderbufferStorageMultisample(GL_RENDERBUFFER, nSamples, GL_DEPTH24_STENCIL8, width(), height()); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); glBindRenderbuffer(GL_RENDERBUFFER, 0); status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { std::cout << "Framebuffer is incomplete: " << status << std::endl; } #else fbo = 0; #endif glBindFramebuffer(GL_FRAMEBUFFER, fboSinglePixel); glBindTexture(GL_TEXTURE_2D, pickingTextureSinglePixel); glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, 1, 1, 0, GL_RED_INTEGER, GL_INT, nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pickingTextureSinglePixel, 0); status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { std::cout << "Single-pixel framebuffer is incomplete: " << status << std::endl; } glBindFramebuffer(GL_FRAMEBUFFER, 0); return true; } nanogui::Slider* AddWidgetWidthSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::Widget*& outWidget) { outWidget = new nanogui::Widget(parent); outWidget->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); new nanogui::Label(outWidget, caption); auto slider = new nanogui::Slider(outWidget); slider->setFixedWidth(100); slider->setValue(defaultValue); slider->setRange(range); return slider; } nanogui::Slider* AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue) { nanogui::Widget* widget; return AddWidgetWidthSlider(parent, caption, range, defaultValue, widget); } nanogui::Slider* AddLabeledSlider(nanogui::Widget* parent, const std::string& caption, const std::pair& range, float defaultValue, nanogui::TextBox*& out_label) { nanogui::Widget* widget; auto slider = AddWidgetWidthSlider(parent, caption, range, defaultValue, widget); out_label = new nanogui::TextBox(widget); out_label->setFixedSize(Eigen::Vector2i(50, 25)); return slider; } void Viewer::SetupGUI() { auto ctx = nvgContext(); auto mainWindow = new nanogui::Window(this, "Regular Mesh Texturing"); mainWindow->setPosition(Eigen::Vector2i(15, 15)); mainWindow->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); auto loadingOptionsBtn = new nanogui::PopupButton(mainWindow, "Loading Options"); loadingOptionsBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 10, 4)); nanogui::TextBox* angleThresholdLabel; angleThresholdSld = AddLabeledSlider(loadingOptionsBtn->popup(), "Angle Threshold:", std::make_pair(1, 45), 20, angleThresholdLabel); angleThresholdSld->setCallback([angleThresholdLabel](float value) { std::stringstream ss; ss.precision(2); ss << value << "\302\260"; //\302\260 = ° angleThresholdLabel->setValue(ss.str()); }); angleThresholdSld->callback()(angleThresholdSld->value()); chkMergeTriangles = new nanogui::CheckBox(loadingOptionsBtn->popup(), "Merge Triangulated Quads"); chkFocusOnModel = new nanogui::CheckBox(loadingOptionsBtn->popup(), "Focus On Model"); chkFocusOnModel->setChecked(true); chkScaleTex = new nanogui::CheckBox(loadingOptionsBtn->popup(), "Scale Texture Coordinates"); chkScaleTex->setChecked(true); auto loadVisBtn = new nanogui::Button(loadingOptionsBtn->popup(), "Load Mesh for Vis"); loadVisBtn->setCallback([&]() { isFileDialogOpen = true; std::string filename = nanogui::file_dialog({ { "obj", "OBJ" } }, false); if (filename != "") LoadMeshForVisualization(filename); isFileDialogOpen = false; }); auto loadBtn = new nanogui::Button(mainWindow, "Load Mesh"); loadBtn->setCallback([&]() { isFileDialogOpen = true; std::string filename = nanogui::file_dialog({ {"ply", "PLY"}, {"obj", "OBJ"} }, false); if (filename != "") LoadMesh(filename); isFileDialogOpen = false; }); //auto processDirectoryBtn = new nanogui::Button(mainWindow, "Process Directory"); //processDirectoryBtn->setCallback([this]() { ProcessDirectory(); }); auto saveBtn = new nanogui::Button(mainWindow, "Save Untextured Mesh"); saveBtn->setCallback([&]() { isFileDialogOpen = true; std::string filename = nanogui::file_dialog({ { "ply", "PLY" } }, true); if (!filename.empty()) { if (filename.substr(filename.length() - 4) != ".ply") filename.append(".ply"); if (filename != "") data.SaveMeshPLY(filename); } isFileDialogOpen = false; }); auto saveBtnObj = new nanogui::Button(mainWindow, "Save Textured Mesh"); saveBtnObj->setCallback([&]() { isFileDialogOpen = true; std::string filename = nanogui::file_dialog({ { "obj", "OBJ" } }, true); if (!filename.empty()) { if (filename.substr(filename.length() - 4) != ".obj") filename.append(".obj"); if (filename != "") { data.SaveMeshOBJ(filename); glViewport(0, 0, width(), height()); } } isFileDialogOpen = false; }); auto displayOptionsBtn = new nanogui::PopupButton(mainWindow, "Display Options"); displayOptionsBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 10, 4)); chkShowFaces = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Faces"); chkShowFaces->setChecked(true); chkShowWireframe = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Wireframe"); chkShowWireframe->setChecked(true); sldWireframeWidth = AddLabeledSlider(displayOptionsBtn->popup(), "Wireframe Thickness", std::make_pair(0.1f, 15.0f), 1.0f); sldWireframeWidth->setFixedWidth(250); chkShowSingularities = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Singularities"); chkShowSingularities->setChecked(true); sldSingularitySize = AddLabeledSlider(displayOptionsBtn->popup(), "Singularity Size", std::make_pair(0.1f, 10.0f), 1.0f); sldSingularitySize->setCallback([this](float) { UploadSingularities(); }); sldSingularitySize->setFixedWidth(250); chkShowMotorcycleGraph = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Motorcycle Graph"); chkShowMotorcycleGraph->setChecked(true); chkShowMotorcycleIndices = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Motorcycle Indices"); chkShowFencedRegions = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Fenced Regions"); chkShowFencedRegions->setChecked(true); chkShowFencedRegionIndices = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Fenced Region Indices"); chkShowPatches = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Patches"); chkShowPatches->setChecked(true); chkShowUV = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show UV"); chkShowUV->setChecked(true); chkShowBrokenArcs = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Broken Arc Constraints"); chkShowVertexIndices = new nanogui::CheckBox(displayOptionsBtn->popup(), "Show Vertex Indices"); cmbPatchColoring = new nanogui::ComboBox(displayOptionsBtn->popup(), { "Color by Patch Index", "Color by Patch Degree" }); cmbPatchColoring->setCallback([this](bool) { this->UploadPatches(); }); sldTubeWidth = AddLabeledSlider(displayOptionsBtn->popup(), "Motorcycle Graph Thickness", std::make_pair(0.1f, 20.0f), 1.0f); sldTubeWidth->setFixedWidth(250); sldParameterizationGridSize = AddLabeledSlider(displayOptionsBtn->popup(), "Param Grid Size", std::make_pair(-1.0f, 8.0f), 2.0f); (new nanogui::Widget(mainWindow))->setHeight(10); //spacing auto quadLayoutOptionsBtn = new nanogui::PopupButton(mainWindow, "Quad Layout Options"); quadLayoutOptionsBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 10, 4)); chkClassifySingularities = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Classify All Singularities"); chkClassifySingularities->setChecked(true); chkMergeFencedRegion = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Merge Fenced Regions"); chkMergeFencedRegion->setChecked(true); chkCalculateMotorcycleGraph = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Calculate Motorcycle Graph"); chkCalculateMotorcycleGraph->setChecked(true); chkDeactivateUnnecessaryMotorcycles = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Deactivate Unnecessary Motorcycles"); chkDeactivateUnnecessaryMotorcycles->setChecked(true); chkExtractPatches = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Extract Patches"); chkExtractPatches->setChecked(true); chkSplitNonRectangularPatches = new nanogui::CheckBox(quadLayoutOptionsBtn->popup(), "Split Non-Rectangular Patches"); chkSplitNonRectangularPatches->setChecked(true); auto quadLayoutBtn = new nanogui::Button(mainWindow, "Calculate Quad Layout"); quadLayoutBtn->setCallback([this]() { ExtractQuadLayout(); }); (new nanogui::Widget(mainWindow))->setHeight(10); //spacing auto parametrizationOptionsBtn = new nanogui::PopupButton(mainWindow, "Parametrization Options"); parametrizationOptionsBtn->popup()->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 10, 4)); nanogui::TextBox* txtParametricEdgeLength; sldParametricEdgeLength = AddLabeledSlider(parametrizationOptionsBtn->popup(), "Parametric Edge Length:", std::make_pair(-3, 3), std::log(10.0f), txtParametricEdgeLength); sldParametricEdgeLength->setCallback([txtParametricEdgeLength](float value) { auto length = std::exp(value); std::stringstream ss; ss.precision(2); ss << length; txtParametricEdgeLength->setValue(ss.str()); }); sldParametricEdgeLength->callback()(sldParametricEdgeLength->value()); nanogui::TextBox* txtParametrizationErrorThreshold; auto sldParametrizationErrorThreshold = AddLabeledSlider(parametrizationOptionsBtn->popup(), "Parametrization Error Threshold:", std::make_pair(1, 100), 50, txtParametrizationErrorThreshold); sldParametrizationErrorThreshold->setCallback([this, txtParametrizationErrorThreshold](float value) { parametrizationErrorThreshold = value; if (value == 100) parametrizationErrorThreshold = std::numeric_limits::infinity(); std::stringstream ss; ss.precision(2); ss << parametrizationErrorThreshold; txtParametrizationErrorThreshold->setValue(ss.str()); }); sldParametrizationErrorThreshold->callback()(sldParametrizationErrorThreshold->value()); nanogui::TextBox* txtOptimizationTimeLimit; auto sldOptimizationTimeLimit = AddLabeledSlider(parametrizationOptionsBtn->popup(), "Optimization Time Limit:", std::make_pair(1.0 / 60.0, 120.0), 5.0, txtOptimizationTimeLimit); sldOptimizationTimeLimit->setCallback([this, txtOptimizationTimeLimit](float value) { std::stringstream ss; ss << std::fixed; ss.precision(1); if (value == 120) { discreteOptimizationTimeLimit = std::chrono::steady_clock::duration::max(); ss << "unlimited"; } else { discreteOptimizationTimeLimit = std::chrono::seconds((int)std::round(value * 60)); ss << value << " min"; } txtOptimizationTimeLimit->setValue(ss.str()); }); sldOptimizationTimeLimit->callback()(sldOptimizationTimeLimit->value()); txtOptimizationTimeLimit->setFixedWidth(90); sldOptimizationTimeLimit->setFixedWidth(250); nanogui::TextBox* txtMipLevel; auto sldMipLevel = AddLabeledSlider(parametrizationOptionsBtn->popup(), "Texture Layout for MIP Level up to:", std::make_pair(0, 8), 0, txtMipLevel); sldMipLevel->setCallback([this, txtMipLevel](float value) { mipLevel = (int)std::round(value); std::stringstream ss; ss << mipLevel; txtMipLevel->setValue(ss.str()); }); sldMipLevel->callback()(sldMipLevel->value()); chkInvisibleSeams = new nanogui::CheckBox(parametrizationOptionsBtn->popup(), "Invisible Seams"); #ifndef WITH_GUROBI chkInvisibleSeams->setEnabled(false); chkInvisibleSeams->setCaption("Invisible Seams are not supported because Gurobi is not available."); #endif // !WITH_GUROBI chkPackTexture = new nanogui::CheckBox(parametrizationOptionsBtn->popup(), "Pack Texture"); auto parameterizationBtn = new nanogui::Button(mainWindow, "Calculate Parametrization"); parameterizationBtn->setCallback([this]() { if (data.Motorcycles() == nullptr || data.Motorcycles()->Patches().size() == 0) { new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Parametrization", "A quad layout is needed to calculate the parametrization."); return; } int mipFactor = 1 << mipLevel; ArclengthStrategy arclengthStrategy = chkInvisibleSeams->checked() ? ArclengthStrategy::GurobiGlobal : ArclengthStrategy::Simple; data.CalculateParametrization(std::exp(sldParametricEdgeLength->value()) / mipFactor, parametrizationErrorThreshold, discreteOptimizationTimeLimit, arclengthStrategy); Statistics ratios, mips; data.EvaluateParametrization(ratios, mips, true); UploadPatches(); UploadBrokenArcs(*data.Motorcycles(), data.BrokenHalfarcs()); if (chkPackTexture->checked()) data.PackTexture(mipFactor); }); /*(new nanogui::Widget(mainWindow))->setHeight(10); //spacing auto renderMeshColorsTextureBtn = new nanogui::Button(mainWindow, "Render Mesh Colors to Texture"); renderMeshColorsTextureBtn->setCallback([this]() { isFileDialogOpen = true; std::string colorsFilename = nanogui::file_dialog({ { "color", "Mesh Colors" } }, false); if (!colorsFilename.empty()) { data.RenderMeshColorsToTexture(colorsFilename); glViewport(0, 0, width(), height()); } isFileDialogOpen = false; }); auto remeshBtn = new nanogui::Button(mainWindow, "Remesh"); remeshBtn->setCallback([this]() { data.Remesh(); }); auto tangentialSmoothBtn = new nanogui::Button(mainWindow, "Tangential Smooth"); tangentialSmoothBtn->setCallback([this]() { data.TangentialSmooth(); meshVertices.uploadData(data.Vertices()); });*/ auto focusOnPointBtn = new nanogui::Button(displayOptionsBtn->popup(), "Focus on Point"); focusOnPointBtn->setCallback([this]() { auto window = new nanogui::Window(this, "Focus on Point"); window->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); auto widget = new nanogui::Widget(window); widget->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Fill, 4, 4)); auto& focusPoint = this->camera().GetFocusPoint(); auto txtX = new nanogui::TextBox(widget, std::to_string(focusPoint.x())); txtX->setEditable(true); auto txtY = new nanogui::TextBox(widget, std::to_string(focusPoint.y())); txtY->setEditable(true); auto txtZ = new nanogui::TextBox(widget, std::to_string(focusPoint.z())); txtZ->setEditable(true); auto btnFocus = new nanogui::Button(window, "Focus"); btnFocus->setCallback([this, txtX, txtY, txtZ, window]() { this->updateFocus(nullptr); try { this->camera().FocusOnPoint(Eigen::Vector3f(std::stof(txtX->value()), std::stof(txtY->value()), std::stof(txtZ->value()))); this->removeChild(window); } catch (...) { std::cerr << "Cannot update camera focus point." << std::endl; } }); performLayout(nvgContext()); window->setPosition(Eigen::Vector2i(this->width() / 2 - window->width() / 2, this->height() / 2 - window->height() / 2) ); performLayout(nvgContext()); }); auto focusOnVertexBtn = new nanogui::Button(displayOptionsBtn->popup(), "Focus on Vertex"); focusOnVertexBtn->setCallback([this]() { auto window = new nanogui::Window(this, "Focus on Point"); window->setLayout(new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); auto txt = new nanogui::TextBox(window, "0"); txt->setEditable(true); auto btnFocus = new nanogui::Button(window, "Focus"); btnFocus->setCallback([this, txt, window]() { this->updateFocus(nullptr); try { auto v = data.Mesh().vertex_handle(std::stoi(txt->value())); auto& p = data.Mesh().point(v); this->camera().FocusOnPoint(Eigen::Vector3f(p[0], p[1], p[2])); this->removeChild(window); } catch (...) { std::cerr << "Cannot update camera focus point." << std::endl; } }); performLayout(nvgContext()); window->setPosition(Eigen::Vector2i(this->width() / 2 - window->width() / 2, this->height() / 2 - window->height() / 2)); performLayout(nvgContext()); }); //auto screenshotBtn = new nanogui::Button(mainWindow, "Take Screenshot"); //screenshotBtn->setCallback([this]() //{ // const int screenshotWidth = 4096; // const int screenshotHeight = screenshotWidth * height() / width(); // isFileDialogOpen = true; // std::string filename = nanogui::file_dialog({ { "png", "PNG" } }, true); // if (!filename.empty()) // { // if (filename.substr(filename.length() - 4) != ".png") // filename.append(".png"); // TakeScreenshot(filename, screenshotWidth, screenshotHeight); // } // isFileDialogOpen = false; //}); //auto makeTurntableBtn = new nanogui::Button(mainWindow, "Render Turntable"); //makeTurntableBtn->setCallback([this]() //{ // camera().MakeHorizontal(); // int frames = 30 /*fps*/ * 5 /*seconds per turn*/; // auto rot = Eigen::Quaternionf(Eigen::AngleAxisf(2 * M_PI / frames, Eigen::Vector3f::UnitY())); // for (int i = 0; i < frames; ++i) // { // TakeScreenshot(std::string("turntable_") + std::to_string(i) + ".png", 1920, 1080); // camera().RotateAroundFocusPointGlobal(rot); // } //}); performLayout(ctx); } void Viewer::TakeScreenshot(const std::string& filename, int screenshotWidth, int screenshotHeight) { GLuint textureFBO, textureColor, depthBuffer; glGenFramebuffers(1, &textureFBO); glBindFramebuffer(GL_FRAMEBUFFER, textureFBO); glGenRenderbuffers(1, &textureColor); glBindRenderbuffer(GL_RENDERBUFFER, textureColor); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, screenshotWidth, screenshotHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, textureColor); glGenRenderbuffers(1, &depthBuffer); glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, screenshotWidth, screenshotHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); glClearColor(1, 1, 1, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glViewport(0, 0, screenshotWidth, screenshotHeight); Eigen::Matrix4f model, view, proj; _camera.ComputeCameraMatrices(view, proj, (float)screenshotWidth / screenshotHeight); render(view, proj, false); std::vector pixels(screenshotWidth * screenshotHeight * 4); glReadPixels(0, 0, screenshotWidth, screenshotHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); glViewport(0, 0, width(), height()); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &textureFBO); glDeleteRenderbuffers(1, &textureColor); glDeleteRenderbuffers(1, &depthBuffer); stbi_write_png(filename.c_str(), screenshotWidth, screenshotHeight, 4, pixels.data() + (screenshotHeight - 1) * screenshotWidth * 4 * sizeof(unsigned char), -screenshotWidth * 4 * sizeof(unsigned char)); } void Viewer::LoadMesh(const std::string& filename) { indexCount = 0; wireframeIndexCount = 0; singularityCount = 0; for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = 0; fencedRegionFaceIndexCount[i] = 0; } motorcycleIndexCount.clear(); patchIndexCount = 0; brokenArcsIndexCount = 0; data.LoadMesh(filename, chkMergeTriangles->checked(), angleThresholdSld->value() * (float)M_PI / 180.0f); if (chkFocusOnModel->checked()) camera().FocusOnBBox(data.MeshBoundingBox()); { nse::util::TimedBlock b("Uploading data.Mesh() to GPU .."); ShaderPool::Instance()->FlatMeshShader.bind(); meshVAO.generate(); meshVAO.bind(); meshVertices.uploadData(data.Vertices()).bindToAttribute("position"); UploadMeshFaces(); meshVAO.unbind(); } UploadWireframeToGPU(); UploadSingularities(); } void Viewer::LoadMeshForVisualization(const std::string& filename) { indexCount = 0; wireframeIndexCount = 0; singularityCount = 0; for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = 0; fencedRegionFaceIndexCount[i] = 0; } motorcycleIndexCount.clear(); patchIndexCount = 0; brokenArcsIndexCount = 0; data.LoadMeshForVisualization(filename, chkScaleTex->checked()); if(chkFocusOnModel->checked()) camera().FocusOnBBox(data.MeshBoundingBox()); { nse::util::TimedBlock b("Uploading data.Mesh() to GPU .."); ShaderPool::Instance()->FlatMeshShader.bind(); meshVAO.generate(); meshVAO.bind(); meshVertices.uploadData(data.Vertices()).bindToAttribute("position"); UploadMeshFaces(); meshVAO.unbind(); } UploadWireframeToGPU(); UploadSingularities(); UploadPatches(); } void Viewer::ExtractQuadLayout() { nse::util::TimedBlock b("Calculating quad layout .."); if (chkClassifySingularities->checked()) data.ClassifyAllSingularities(); if (chkMergeFencedRegion->checked()) data.TryToMergePatches(); if (chkCalculateMotorcycleGraph->checked()) data.CalculateMotorcycleGraph(chkDeactivateUnnecessaryMotorcycles->checked()); UploadFencedRegions(data.FencedRegions().patches.begin(), data.FencedRegions().patches.end()); UploadSingularities(); if(data.Motorcycles() != nullptr) UploadMotorcycles(*data.Motorcycles()); if (data.Motorcycles() == nullptr || data.Motorcycles()->Status() != MotorcycleGraph::Finished) return; if (chkExtractPatches->checked()) { auto stats = data.ExtractPatches(chkSplitNonRectangularPatches->checked()); b.closeBlock(); std::sort(stats.patchSizes.begin(), stats.patchSizes.end()); std::cout << stats; UploadPatches(); int nonOriginalEdges = 0; for (auto& patch : data.Motorcycles()->Patches()) { for (auto& side : patch.PatchSides()) { for (auto arcIdx : side) { auto& arc = data.Motorcycles()->Halfarcs()[arcIdx]; for (auto p : arc) { auto e = data.Mesh().edge_handle(data.Motorcycles()->MotorcycleHalfedge(p)); if (!data.IsEdgeOriginal(e)) ++nonOriginalEdges; } } } } std::cout << "Used " << nonOriginalEdges / 2 << " non-original edges." << std::endl; //every edge is counted twice } } void Viewer::ProcessDirectory() { bool evaluateOriginalMCG = true; bool evaluateUnmergedRegions = false; bool evaluateSimpleParametrization = true; bool evaluateInvisibleSeamParametrization = true; bool renderSnippet = false; bool packTexture = true; bool saveResult = false; std::string filename = nanogui::file_dialog({ { "obj", "Wavefront OBJ" }, { "ply", "Stanford PLY" }, }, false); std::string directory = nse::data::parent_path(filename); std::vector files; nse::data::files_in_dir(directory, files); //for (parametrizationErrorThreshold = 30; parametrizationErrorThreshold <= 90; parametrizationErrorThreshold += 10) { std::vector modelsWithNonRectangles; std::vector modelsWithFailedTracing; std::vector modelsWithTimeLimitExceeded; Statistics logAreaRatios, mips; std::ofstream csv("stats_ParamError" + std::to_string((int)parametrizationErrorThreshold) + ".csv"); csv << "file;vertices;faces;edges;singularities;"; if (evaluateOriginalMCG) csv << "Patches With Simple MCG;"; csv << "Classification Time;"; if (evaluateUnmergedRegions) csv << "Meta Singularities; Patches With Classification;"; csv << "Merge Time; Meta Singularities; Trace Time; Extract Time; Patches With Merge; Non-original edges;Original edges;"; if (evaluateSimpleParametrization) csv << "Simple Parametrization Time; Ratio Percentiles 5-95;;;;;;;;;;;;;;;;;;;MIPS avg; MIPS 90-percentile;"; if (evaluateInvisibleSeamParametrization) csv << "Invisible Seam Parametrization Time; Ratio Percentiles 5-95;;;;;;;;;;;;;;;;;;;MIPS avg; MIPS 90-percentile; Broken Arcs; BrokenArcLength; TotalArcs; TotalArcLength;"; if (packTexture) csv << "Texture Packing Time; "; csv << std::endl; for (auto& entry : files) { if (!nse::data::is_directory(entry)) { if ((nse::data::extension(entry) == ".obj" || nse::data::extension(entry) == ".ply") && !nse::data::str_ends_with(entry, ".textured.obj")) data.LoadMesh(entry, false, 0); else continue; csv << entry << ";" << data.Vertices().cols() << ";" << data.Faces().size() << ";" << data.Mesh().n_edges() << ";" << data.Singularities().size() << ";"; nse::util::TimedBlock b("Processing file " + entry + " .."); MotorcycleGraph::ExtractionStatistics stats; try { if (evaluateOriginalMCG) { data.CalculateMotorcycleGraph(); stats.Clear(); stats = data.ExtractPatches(false); csv << data.Motorcycles()->Patches().size() << ";"; } { nse::util::TimedBlock b("Classify .."); data.ClassifyAllSingularities(); csv << b.time() << ";"; } if (evaluateUnmergedRegions) { data.FindMetaSingularities(); csv << data.MetaSingularities().size() << ";"; data.CalculateMotorcycleGraph(); stats = data.ExtractPatches(false); csv << data.Motorcycles()->Patches().size() << ";"; } { nse::util::TimedBlock b("Merge .."); data.TryToMergePatches(); csv << b.time() << ";"; } { nse::util::TimedBlock b("Tracing .."); data.CalculateMotorcycleGraph(true); auto t = b.time(); csv << data.MetaSingularities().size() << ";"; csv << t << ";"; } if (data.Motorcycles()->Status() != MotorcycleGraph::Finished) { modelsWithFailedTracing.push_back(entry); csv << std::endl; continue; } stats.Clear(); { nse::util::TimedBlock b("Extracting .."); stats = data.ExtractPatches(true); csv << b.time() << ";"; } csv << data.Motorcycles()->Patches().size() << ";"; bool hasNonRectangles = false; for (int i = 0; i < stats.patchCountPerNumberOfCorners.size(); ++i) { if (i != 4 && stats.patchCountPerNumberOfCorners[i] > 0) hasNonRectangles = true; } if (hasNonRectangles) modelsWithNonRectangles.push_back(entry); int nonOriginalEdges = 0; int originalEdges = 0; for (auto& patch : data.Motorcycles()->Patches()) { for (auto& side : patch.PatchSides()) { for (auto arcIdx : side) { auto& arc = data.Motorcycles()->Halfarcs()[arcIdx]; for (auto p : arc) { auto e = data.Mesh().edge_handle(data.Motorcycles()->MotorcycleHalfedge(p)); if (!data.IsEdgeOriginal(e)) ++nonOriginalEdges; else ++originalEdges; } } } } csv << nonOriginalEdges / 2 << ";" << originalEdges << ";"; if (evaluateSimpleParametrization) { { nse::util::TimedBlock b("Simple Parametrization .."); data.CalculateParametrization(10.0f, parametrizationErrorThreshold, discreteOptimizationTimeLimit, Simple); csv << b.time() << ";"; } logAreaRatios.Clear(); data.EvaluateParametrization(logAreaRatios, mips, false); for (int i = 1; i < 20; ++i) { float percent = i * 0.05; auto percentile = logAreaRatios.Percentile(percent); csv << std::exp(percentile) << ";"; } csv << mips.Average() << ";" << mips.Percentile(0.9f) << ";"; } if (renderSnippet) { camera().FocusOnBBox(data.MeshBoundingBox()); ShaderPool::Instance()->FlatMeshShader.bind(); meshVAO.generate(); meshVAO.bind(); meshVertices.uploadData(data.Vertices()).bindToAttribute("position"); UploadMeshFaces(); meshVAO.unbind(); UploadPatches(); TakeScreenshot(entry + ".png", 512, 512 * height() / width()); } if (evaluateInvisibleSeamParametrization) { { nse::util::TimedBlock b("Invisible Seam Parametrization .."); data.CalculateParametrization(10.0f, parametrizationErrorThreshold, discreteOptimizationTimeLimit, GurobiGlobal); csv << b.time() << ";"; } logAreaRatios.Clear(); data.EvaluateParametrization(logAreaRatios, mips, false); for (int i = 1; i < 20; ++i) { float percent = i * 0.05; float percentile = logAreaRatios.Percentile(percent); csv << std::exp(percentile) << ";"; } csv << mips.Average() << ";" << mips.Percentile(0.9f) << ";"; int totalArcLength = 0, totalBrokenArclength = 0; for (auto& arc : data.Motorcycles()->Halfarcs()) for (auto p : arc) ++totalArcLength; for (auto idx : data.BrokenHalfarcs()) for (auto p : data.Motorcycles()->Halfarcs()[idx]) ++totalBrokenArclength; csv << data.BrokenHalfarcs().size() << ";" << totalBrokenArclength << ";" << data.Motorcycles()->Halfarcs().size() / 2 << ";" << totalArcLength / 2 << ";"; } if (packTexture) { nse::util::TimedBlock b("Packing .."); data.PackTexture(1 << mipLevel); csv << b.time() << ";"; } //try //{ // data.CalculateParametrization(10.0f, AllInvalid, GurobiGlobal); //} //catch (std::runtime_error& e) //{ // std::cout << e.what() << std::endl; // modelsWithTimeLimitExceeded.push_back(entry); // continue; //} //if(packTexture) // data.PackTexture(); if (saveResult) data.SaveMeshOBJ(entry + ".textured.obj"); } catch (std::runtime_error& error) { std::cout << "Runtime Error: " << error.what() << std::endl; } catch (...) { std::cout << "Unknown error." << std::endl; } csv << std::endl; data.Clear(); } } csv.close(); std::cout << "Models with non-rectangles: " << modelsWithNonRectangles.size() << std::endl; for (auto& entry : modelsWithNonRectangles) std::cout << " " << entry << std::endl; std::cout << "Models with failed tracing: " << modelsWithFailedTracing.size() << std::endl; for (auto& entry : modelsWithFailedTracing) std::cout << " " << entry << std::endl; std::cout << "Models with time limit exceeded: " << modelsWithTimeLimitExceeded.size() << std::endl; for (auto& entry : modelsWithTimeLimitExceeded) std::cout << " " << entry << std::endl; } } //EmitVertexFunctor: void(const HEMesh::HalfedgeHandle[3]) //the to-vertices of the halfedges are the triangle corners template void TriangulateMeshFace(HEMesh::FaceHandle f, const HEMesh& mesh, EmitTriangleFunctor&& emitTriangle) { OpenMesh::HalfedgeHandle base; for (auto h : mesh.fh_range(f)) { if (base.idx() == -1) { base = h; continue; } auto nextH = mesh.next_halfedge_handle(h); if (nextH == base) break; else { HEMesh::HalfedgeHandle triangle[3] = { base, h, nextH }; std::forward(emitTriangle)(triangle); } } } void Viewer::UploadMeshFaces() { std::vector indices; indices.reserve(4 * data.Faces().size()); for (auto f : data.Mesh().faces()) //for each face { TriangulateMeshFace(f, data.Mesh(), [&](const HEMesh::HalfedgeHandle h[3]) { indices.push_back(data.Mesh().to_vertex_handle(h[0]).idx()); indices.push_back(data.Mesh().to_vertex_handle(h[1]).idx()); indices.push_back(data.Mesh().to_vertex_handle(h[2]).idx()); }); } indexCount = (unsigned int)indices.size(); meshIndices.uploadData(indexCount, 1, sizeof(unsigned int), GL_UNSIGNED_INT, true, reinterpret_cast(indices.data())); meshIndices.bind(); } void Viewer::UploadWireframeToGPU() { nse::util::TimedBlock b("Uploading wireframe to GPU .."); std::vector indices; for (auto& e : data.Mesh().edges()) { if (!data.IsEdgeOriginal(e)) continue; auto h0 = data.Mesh().halfedge_handle(e, 0); auto h1 = data.Mesh().halfedge_handle(e, 1); auto v0 = data.Mesh().to_vertex_handle(h0); auto v1 = data.Mesh().to_vertex_handle(h1); indices.push_back(v0.idx()); indices.push_back(v1.idx()); } wireframeIndexCount = (unsigned int)indices.size(); ShaderPool::Instance()->SimpleShader.bind(); wireframeVAO.generate(); wireframeVAO.bind(); meshVertices.bindToAttribute("position"); wireframeIndices.uploadData(indices.size(), 1, sizeof(unsigned int), GL_UNSIGNED_INT, true, reinterpret_cast(indices.data())); wireframeVAO.unbind(); } void Viewer::UploadSingularities() { std::vector singPos, singCol; primitiveIdToSingularityId.clear(); auto addVertex = [&](HEMesh::VertexHandle v, bool realSingularity, HEMesh::HalfedgeHandle contextOutgoingEdge, int valenceDefect) { float radius = 0; int edgeValence = 0; for (auto h : data.Mesh().voh_range(v)) { radius += data.Mesh().calc_edge_length(h); ++edgeValence; } radius /= edgeValence; radius *= (realSingularity ? 0.3f : 0.1f) * sldSingularitySize->value(); auto p = data.Mesh().point(v); if (contextOutgoingEdge.is_valid()) p = p + data.Mesh().calc_edge_vector(contextOutgoingEdge).normalized() * radius; singPos.emplace_back(); for (int j = 0; j < 3; ++j) singPos.back()(j) = p[j]; singPos.back()(3) = radius; singCol.push_back(realSingularity ? GetValenceColor(valenceDefect) : Eigen::Vector4f(0.1f, 0.8f, 0.1f, 1.0f)); }; for (int i = 0; i < data.Singularities().size(); ++i) { if (data.MetaSingularities().size() == 0) { addVertex(data.Singularities()[i].vertexHandle, true, data.Singularities()[i].contextOutgoingBoundaryEdge, data.Singularities()[i].valenceDefect); primitiveIdToSingularityId[singPos.size() - 1] = i; } else { bool realSingularity = false; const size_t* ptr; if (!realSingularity) { if (data.Singularities()[i].contextOutgoingBoundaryEdge.is_valid()) realSingularity = data.VertexToMetaSingularity().TryAccessAtContextEdge(data.Singularities()[i].contextOutgoingBoundaryEdge, ptr); else realSingularity = data.VertexToMetaSingularity().TryAccessAtManifoldVertex(data.Singularities()[i].vertexHandle, ptr); } if (!realSingularity) { addVertex(data.Singularities()[i].vertexHandle, realSingularity, data.Singularities()[i].contextOutgoingBoundaryEdge, data.Singularities()[i].valenceDefect); primitiveIdToSingularityId[singPos.size() - 1] = i; } } } for (auto& s : data.MetaSingularities()) { auto valence = s.Degree(); for (int color = 0; color < valence; ++color) { auto& motorcycle = s.GetEmanatingMotorcycle(color); if (motorcycle.size() > 0) { auto e = motorcycle.front(); HEMesh::HalfedgeHandle context; if (!data.Mesh().is_manifold(data.Mesh().from_vertex_handle(e))) { context = data.Mesh().opposite_halfedge_handle(e); CirculateBackwardUntil(context, data.Mesh(), [&](HEMesh::HalfedgeHandle) {return false; }); } addVertex(data.Mesh().from_vertex_handle(e), true, context, valence - 4); const size_t* singIdx; if(data.VertexToMetaSingularity().TryAccessAtToVertex(data.Mesh().opposite_halfedge_handle(e), singIdx)) primitiveIdToSingularityId[singPos.size() - 1] = *singIdx; break; } } } ShaderPool::Instance()->SphereShader.bind(); singularityVAO.generate(); singularityVAO.bind(); singularityPositions.uploadData(singPos).bindToAttribute("positionRadius"); singularityColors.uploadData(singCol).bindToAttribute("color"); singularityVAO.unbind(); singularityCount = singPos.size(); } int32_t Viewer::GetPickingIndex(const Eigen::Vector2i p) const { if (p.x() >= 0 && p.y() >= 0 && p.x() < width() && p.y() < height()) { glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboSinglePixel); glDrawBuffer(GL_COLOR_ATTACHMENT0); glReadBuffer(GL_COLOR_ATTACHMENT1); //copy the interesting pixel to a non-multisample frame buffer auto readX = p.x(); auto readY = height() - 1 - p.y(); glBlitFramebuffer(readX, readY, readX + 1, readY + 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); //copy the pixel to CPU memory glBindFramebuffer(GL_READ_FRAMEBUFFER, fboSinglePixel); glReadBuffer(GL_COLOR_ATTACHMENT0); int32_t index; glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &index); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); return index; } else return -1; } bool Viewer::mouseMotionHook(const Eigen::Vector2i & p, const Eigen::Vector2i & rel, int button, int modifiers) { return false; } bool Viewer::mouseButtonHook(const Eigen::Vector2i & p, int button, bool down, int modifiers) { if (isFileDialogOpen) return true; if (!down && button == 0 && modifiers == GLFW_MOD_CONTROL) { int pickedSingularity = -1; auto singIt = primitiveIdToSingularityId.find(GetPickingIndex(p)); if (singIt != primitiveIdToSingularityId.end()) pickedSingularity = singIt->second; if (pickedSingularity != -1) { FencedRegion calculatedPatch(data.Mesh(), data.VertexToSingularity()); const FencedRegion* patchPtr; selectedRegion = (size_t)-1; if (data.Singularities()[pickedSingularity].correspondingRegion != (size_t)-1) { selectedRegion = data.Singularities()[pickedSingularity].correspondingRegion; patchPtr = &data.FencedRegions().patches[selectedRegion]; } else { std::cout << "Calculating new patch" << std::endl; auto classificationResult = data.ClassifySingularity(data.Singularities()[pickedSingularity], calculatedPatch); patchPtr = &calculatedPatch; if (classificationResult == NoPatch) { std::cout << "Cannot grow patch." << std::endl; for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = 0; fencedRegionFaceIndexCount[i] = 0; } } } if (!patchPtr->IsRectilinear()) { //MotorcycleGraph data.graph(data.Mesh(), fencedRegions, metaSingularities, vertexToMetaSingularity); //InitializeMotorcyclesForSingularPatch(*patchPtr, data.graph); //UploadMotorcycles(data.graph); } //clicked a singularity std::cout << "Corresponding patch of singularity: " << data.Singularities()[pickedSingularity].correspondingRegion << std::endl; for (auto& l : patchPtr->Loops()) std::cout << "Loop with turn count of " << l.Degree() << std::endl; if(patchPtr->IsRectilinear()) { std::cout << "Patch is rectilinear and covers " << patchPtr->CoveredSingularities().size() << " singularities." << std::endl; } else { std::cout << "Patch is not rectilinear and covers " << patchPtr->CoveredSingularities().size() << " singularities." << std::endl; } UploadFencedRegions(patchPtr, patchPtr + 1, true); return true; } } return false; } template void Viewer::UploadFencedRegions(Iterator itBegin, Iterator itEnd, bool includeDeactivated) { nse::util::TimedBlock b("Uploading fenced regions .."); for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = 0; fencedRegionFaceIndexCount[i] = 0; } Eigen::Matrix indices[3]; std::vector faceIndices[3]; for (auto it = itBegin; it != itEnd; ++it) { const FencedRegion& patch = *it; if (!patch.IsActive() && !includeDeactivated) continue; int matrixIndex; if (patch.Loops()[0].Degree() < 4) matrixIndex = 0; else if (patch.Loops()[0].Degree() == 4) matrixIndex = 1; else matrixIndex = 2; auto patchIndexMatrix = patch.GenerateBoundaryIndices(); indices[matrixIndex].conservativeResize(Eigen::NoChange, indices[matrixIndex].cols() + patchIndexMatrix.cols()); indices[matrixIndex].rightCols(patchIndexMatrix.cols()) = patchIndexMatrix; for (auto f : patch.IncludedFaces()) { TriangulateMeshFace(f, data.Mesh(), [&](HEMesh::HalfedgeHandle h[3]) { for (int i = 0; i < 3; ++i) faceIndices[matrixIndex].push_back(data.Mesh().to_vertex_handle(h[i]).idx()); }); } //auto patchFaceIndexData = patch.GenerateFaceIndices(); //faceIndices[matrixIndex].insert(faceIndices[matrixIndex].end(), patchFaceIndexData.begin(), patchFaceIndexData.end()); } for (int i = 0; i < 3; ++i) { fencedRegionIndexCount[i] = indices[i].size(); fencedRegionFaceIndexCount[i] = faceIndices[i].size(); } indices[0].conservativeResize(Eigen::NoChange, indices[0].cols() + indices[1].cols()); indices[0].rightCols(indices[1].cols()) = indices[1]; indices[0].conservativeResize(Eigen::NoChange, indices[0].cols() + indices[2].cols()); indices[0].rightCols(indices[2].cols()) = indices[2]; faceIndices[0].insert(faceIndices[0].end(), faceIndices[1].begin(), faceIndices[1].end()); faceIndices[0].insert(faceIndices[0].end(), faceIndices[2].begin(), faceIndices[2].end()); ShaderPool::Instance()->SimpleShader.bind(); fencedRegionsVAO.generate(); fencedRegionsVAO.bind(); meshVertices.bind(); meshVertices.bindToAttribute("position"); fencedRegionIndices.uploadData(indices[0]); fencedRegionsVAO.unbind(); fencedRegionsFaceVAO.generate(); fencedRegionsFaceVAO.bind(); meshVertices.bind(); meshVertices.bindToAttribute("position"); fencedRegionFaceIndices.uploadData(faceIndices[0].size(), 1, sizeof(unsigned int), GL_UNSIGNED_INT, true, reinterpret_cast(faceIndices[0].data())); fencedRegionsFaceVAO.unbind(); } void Viewer::UploadPatches() { nse::util::TimedBlock b("Uploading patches .."); data.FindPatchColors(); std::vector positions; std::vector colors; std::vector texCoords; int patchIndex = 0; for (int i = 0; i < data.Motorcycles()->Patches().size(); ++i) { const TexturePatch& patch = data.Motorcycles()->Patches()[i]; const TextureCoordinatesStorage& texCoordStorage = data.AccessTexCoords(i); Eigen::Vector4f color; switch (cmbPatchColoring->selectedIndex()) { case 0: color = data.GetPatchColor(patchIndex++); color.w() = 0.7f; break; case 1: color = GetValenceColor(patch.Corners() - 4); break; } for (HEMesh::FaceHandle f : patch.Faces()) { TriangulateMeshFace(f, data.Mesh(), [&](const HEMesh::HalfedgeHandle h[3]) { for (int i = 0; i < 3; ++i) { auto v = data.Mesh().to_vertex_handle(h[i]); auto pos = data.Mesh().point(v); positions.push_back(Eigen::Vector4f(pos[0], pos[1], pos[2], 1)); colors.push_back(color); texCoords.push_back(texCoordStorage.TexCoordAtToVertex(h[i], data.Mesh())); } }); } /*for (auto& tri : patch.filledHoles) { for (int i = 0; i < 3; ++i) { auto pos = data.Mesh().point(tri[i]); positions.push_back(Eigen::Vector4f(pos[0], pos[1], pos[2], 1)); colors.push_back(color); texCoords.push_back(patch.TexCoordAtInnerVertex(tri[i], data.Mesh())); } }*/ } ShaderPool::Instance()->SimpleShader.bind(); patchVAO.generate(); patchVAO.bind(); patchPositionsVBO.uploadData(positions).bindToAttribute("position"); patchColorsVBO.uploadData(colors).bindToAttribute("color"); patchTexCoordsVBO.uploadData(texCoords).bindToAttribute("texCoords"); patchVAO.unbind(); patchIndexCount = positions.size(); } void Viewer::UploadMotorcycles(const MotorcycleGraph& graph) { nse::util::TimedBlock b("Uploading motorcycles .."); //Upload to GPU motorcycleIndexCount.clear(); std::vector indices; for (auto& cycle : graph.Motorcycles()) { motorcycleIndexCount.push_back(2 * cycle.Path().size()); for (auto h : cycle.Path()) { indices.push_back(data.Mesh().from_vertex_handle(h).idx()); indices.push_back(data.Mesh().to_vertex_handle(h).idx()); } } ShaderPool::Instance()->SimpleShader.bind(); motorcycleVAO.generate(); motorcycleVAO.bind(); meshVertices.bind(); meshVertices.bindToAttribute("position"); motorcycleIndices.uploadData(sizeof(unsigned int) * indices.size(), indices.data()); motorcycleVAO.unbind(); } void Viewer::UploadBrokenArcs(const MotorcycleGraph& graph, const std::vector& brokenArcs) { brokenArcsIndexCount = 0; std::vector indices; for (auto iArc : brokenArcs) { auto& arc = graph.Halfarcs()[iArc]; for (auto segment : arc) { auto h = graph.MotorcycleHalfedge(segment); indices.push_back(data.Mesh().from_vertex_handle(h).idx()); indices.push_back(data.Mesh().to_vertex_handle(h).idx()); } } ShaderPool::Instance()->SimpleShader.bind(); brokenArcsVAO.generate(); brokenArcsVAO.bind(); meshVertices.bind(); meshVertices.bindToAttribute("position"); brokenArcsIndices.uploadData(sizeof(unsigned int) * indices.size(), indices.data()); brokenArcsVAO.unbind(); brokenArcsIndexCount = indices.size(); } void Viewer::drawContents() { glBindFramebuffer(GL_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); Eigen::Matrix4f model, view, proj; _camera.ComputeCameraMatrices(view, proj); render(view, proj); #ifndef LAPTOP_COMPAT glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glReadBuffer(GL_COLOR_ATTACHMENT0); glDrawBuffer(GL_BACK); glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, 0); #endif } void Viewer::DrawTextIfNotHidden(const Eigen::Vector4f& pos, const Eigen::Matrix4f mvp, const std::string& text, float tolerance) { Eigen::Vector4f projectedPosition = mvp * pos; projectedPosition /= projectedPosition.w(); projectedPosition.x() = (projectedPosition.x() + 1) / 2 * width(); projectedPosition.y() = (1 - projectedPosition.y()) / 2 * height(); if (projectedPosition.x() < 0 || projectedPosition.x() >= width() || projectedPosition.y() < 0 || projectedPosition.y() >= height()) return; Eigen::Vector3f reprojectedPosition; float depth = get3DPosition(Eigen::Vector2i((int)projectedPosition.x(), (int)projectedPosition.y()), reprojectedPosition); if ((pos.head<3>() - reprojectedPosition).norm() <= tolerance) nvgText(mNVGContext, projectedPosition.x(), projectedPosition.y(), text.c_str(), nullptr); } void Viewer::render(const Eigen::Matrix4f & mv, const Eigen::Matrix4f & proj, bool background) { glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX); Eigen::Matrix4f mvp = proj * mv; #ifndef LAPTOP_COMPAT GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1 }; if (background) { glDrawBuffers(2, bufs); ShaderPool::Instance()->ClearShader.bind(); glDepthMask(GL_FALSE); emptyVAO.bind(); glDrawArrays(GL_TRIANGLES, 0, 3); emptyVAO.unbind(); glDepthMask(GL_TRUE); glDrawBuffers(1, bufs); } #endif Eigen::Vector3f cameraPosition = mv.topLeftCorner<3, 3>().transpose() * (-mv.topRightCorner<3, 1>()); if (data.Vertices().cols() > 0) { if (chkShowFaces->checked()) { auto& shader = ShaderPool::Instance()->FlatMeshShader; shader.bind(); shader.setUniform("mv", mv); shader.setUniform("mvp", mvp); meshVAO.bind(); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0); meshVAO.unbind(); } if (chkShowWireframe->checked()) { /*auto& shader2 = ShaderPool::Instance()->SimpleShader; shader2.bind(); shader2.setUniform("mvp", mvp); shader2.setUniform("color", Eigen::Vector4f(0.0f, 0.0f, 0.0f, 1.0f)); wireframeVAO.bind(); glDepthRange(0.0, 0.999995f); glDrawElements(GL_LINES, wireframeIndexCount, GL_UNSIGNED_INT, 0); glDepthRange(0.0, 1.0f);*/ auto radius = 0.03f * data.AverageEdgeLength() * sldWireframeWidth->value(); wireframeVAO.bind(); Eigen::Vector4f color(0, 0, 0, 1); auto& shader = ShaderPool::Instance()->CylinderShader; shader.bind(); shader.setUniform("mvp", mvp); shader.setUniform("radius", radius); shader.setUniform("cameraPosition", cameraPosition); shader.setUniform("color", color); shader.setUniform("shaded", 0); glDrawElements(GL_LINES, wireframeIndexCount, GL_UNSIGNED_INT, 0); auto& shader2 = ShaderPool::Instance()->SphereShader; shader2.bind(); shader2.setUniform("mv", mv); shader2.setUniform("p", proj); shader2.setUniform("constantRadius", 1u); shader2.setUniform("radius", radius); shader2.setUniform("constantColor", 1u); shader2.setUniform("color", color); shader2.setUniform("shaded", 0); glDrawElements(GL_POINTS, wireframeIndexCount, GL_UNSIGNED_INT, 0); wireframeVAO.unbind(); } } if (motorcycleIndexCount.size() > 0 && chkShowMotorcycleGraph->checked() && data.Motorcycles() != nullptr) { float globalRadius = 0.05f * data.AverageEdgeLength() * sldTubeWidth->value(); unsigned int start = 0; for (int i = 0; i < motorcycleIndexCount.size(); ++i) { Eigen::Vector4f color = GetSegmentColor(i); float radius = globalRadius * (data.Motorcycles()->Motorcycles()[i].isDeactivated ? 0.3f : 1.0f); auto& shader = ShaderPool::Instance()->CylinderShader; shader.bind(); shader.setUniform("mvp", mvp); shader.setUniform("radius", radius); shader.setUniform("cameraPosition", cameraPosition); shader.setUniform("color", color); shader.setUniform("shaded", 1); motorcycleVAO.bind(); glDrawElements(GL_LINES, motorcycleIndexCount[i], GL_UNSIGNED_INT, (void*)(start * 4)); auto& shader2 = ShaderPool::Instance()->SphereShader; shader2.bind(); shader2.setUniform("mv", mv); shader2.setUniform("p", proj); shader2.setUniform("constantRadius", 1u); shader2.setUniform("radius", radius); shader2.setUniform("constantColor", 1u); shader2.setUniform("color", color); shader2.setUniform("shaded", 1); glDrawElements(GL_POINTS, motorcycleIndexCount[i], GL_UNSIGNED_INT, (void*)(start * 4)); motorcycleVAO.unbind(); start += motorcycleIndexCount[i]; } } if (brokenArcsIndexCount > 0 && chkShowBrokenArcs->checked()) { Eigen::Vector4f color(1.0f, 0.0f, 0.0f, 1.0f); float radius = 0.10f * data.AverageEdgeLength() * sldTubeWidth->value(); auto& shader = ShaderPool::Instance()->CylinderShader; shader.bind(); shader.setUniform("mvp", mvp); shader.setUniform("radius", radius); shader.setUniform("cameraPosition", cameraPosition); shader.setUniform("color", color); shader.setUniform("shaded", 1); brokenArcsVAO.bind(); glDrawElements(GL_LINES, brokenArcsIndexCount, GL_UNSIGNED_INT, 0); auto& shader2 = ShaderPool::Instance()->SphereShader; shader2.bind(); shader2.setUniform("mv", mv); shader2.setUniform("p", proj); shader2.setUniform("constantRadius", 1u); shader2.setUniform("radius", radius); shader2.setUniform("constantColor", 1u); shader2.setUniform("color", color); shader2.setUniform("shaded", 1); glDrawElements(GL_POINTS, brokenArcsIndexCount, GL_UNSIGNED_INT, 0); brokenArcsVAO.unbind(); } if (singularityCount > 0 && chkShowSingularities->checked()) { #ifndef LAPTOP_COMPAT glDrawBuffers(2, bufs); #endif auto& shader = ShaderPool::Instance()->SphereShader; shader.bind(); shader.setUniform("mv", mv); shader.setUniform("p", proj); shader.setUniform("constantRadius", 0u); shader.setUniform("constantColor", 0u); shader.setUniform("shaded", 1); singularityVAO.bind(); glDrawArrays(GL_POINTS, 0, (GLsizei)singularityCount); singularityVAO.unbind(); #ifndef LAPTOP_COMPAT glDrawBuffers(1, bufs); #endif } if (patchIndexCount > 0 && chkShowPatches->checked()) { auto& shader = ShaderPool::Instance()->SimpleShader; shader.bind(); shader.setUniform("mvp", mvp); shader.setUniform("useUniformColor", 0); shader.setUniform("visualizeTexCoords", chkShowUV->checked() ? 1 : 0); shader.setUniform("texCoordScale", std::pow(2.0f, -sldParameterizationGridSize->value())); glDepthRange(0.0, 0.999999f); patchVAO.bind(); glDrawArrays(GL_TRIANGLES, 0, patchIndexCount); patchVAO.unbind(); glDepthRange(0.0, 1.0f); } if (fencedRegionIndexCount[0] + fencedRegionIndexCount[1] + fencedRegionIndexCount[2] > 0 && chkShowFencedRegions->checked()) { float radius = 0.02f * data.AverageEdgeLength() * sldTubeWidth->value(); auto& shader = ShaderPool::Instance()->SimpleShader; shader.bind(); shader.setUniform("mvp", mvp); shader.setUniform("useUniformColor", 1); shader.setUniform("visualizeTexCoords", 0); //(0.933, 0.839, 0.686 //const Eigen::Vector4f colors[] = { Eigen::Vector4f(0.1f, 0.7f, 0.1f, 0.4f), Eigen::Vector4f(0.7f, 0.1f, 0.1f, 0.4f) }; Eigen::Vector4f colors[] = { GetValenceColor(-2), Eigen::Vector4f(0.4f, 0.9f, 0.4f, 1.0f), GetValenceColor(2) }; for (int i = 0; i < 3; ++i) colors[i].w() = 0.6f; glDepthRange(0.0, 0.999998f); fencedRegionsFaceVAO.bind(); unsigned int offset = 0; for (int i = 0; i < 3; ++i) { if (fencedRegionFaceIndexCount[i] > 0) { shader.setUniform("color", colors[i]); glDrawElements(GL_TRIANGLES, fencedRegionFaceIndexCount[i], GL_UNSIGNED_INT, (void*)(offset * 4)); } offset += fencedRegionFaceIndexCount[i]; } glDepthRange(0.0, 1.0f); fencedRegionsVAO.bind(); auto& shader2 = ShaderPool::Instance()->CylinderShader; shader2.bind(); shader2.setUniform("mvp", mvp); shader2.setUniform("radius", radius); shader2.setUniform("cameraPosition", cameraPosition); shader2.setUniform("shaded", 1); offset = 0; for (int i = 0; i < 3; ++i) { if (fencedRegionIndexCount[i] > 0) { auto color = colors[i]; color.w() = 1; shader2.setUniform("color", color); glDrawElements(GL_LINES, fencedRegionIndexCount[i], GL_UNSIGNED_INT, (void*)(offset * 4)); } offset += fencedRegionIndexCount[i]; } auto& shader3 = ShaderPool::Instance()->SphereShader; shader3.bind(); shader3.setUniform("mv", mv); shader3.setUniform("p", proj); shader3.setUniform("constantRadius", 1u); shader3.setUniform("radius", radius); shader3.setUniform("constantColor", 1u); shader3.setUniform("shaded", 1); offset = 0; for (int i = 0; i < 3; ++i) { if (fencedRegionIndexCount[i] > 0) { auto color = colors[i]; color.w() = 1; shader3.setUniform("color", color); glDrawElements(GL_POINTS, fencedRegionIndexCount[i], GL_UNSIGNED_INT, (void*)(offset * 4)); } offset += fencedRegionIndexCount[i]; } fencedRegionsVAO.unbind(); } bool drawRegionIndices = data.FencedRegions().patches.size() > 0 && chkShowFencedRegionIndices->checked(); bool drawMotorcycleIndices = data.Motorcycles() != nullptr && chkShowMotorcycleIndices->checked(); bool drawVertexIndices = data.Vertices().cols() > 0 && chkShowVertexIndices->checked(); if (drawRegionIndices || drawMotorcycleIndices || drawVertexIndices) { #ifndef LAPTOP_COMPAT glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glReadBuffer(GL_COLOR_ATTACHMENT0); glDrawBuffer(GL_BACK); glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); #endif nvgBeginFrame(mNVGContext, mSize[0], mSize[1], mPixelRatio); nvgFontSize(mNVGContext, 14.0f); nvgFontFace(mNVGContext, "sans-bold"); nvgTextAlign(mNVGContext, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); nvgFillColor(mNVGContext, { 0.1f, 0.1f, 0.1f, 1.0f }); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); float tolerance = 0.5 * data.AverageEdgeLength(); if (drawVertexIndices) { for (int i = 0; i < data.Mesh().n_vertices(); ++i) { Eigen::Vector4f pos; pos.setZero(); auto p = data.Mesh().point(data.Mesh().vertex_handle(i)); pos.x() = p[0]; pos.y() = p[1]; pos.z() = p[2]; pos.w() = 1; DrawTextIfNotHidden(pos, mvp, std::to_string(i), tolerance); } } if (drawRegionIndices) { for (int i = 0; i < data.FencedRegions().patches.size(); ++i) { Eigen::Vector4f pos; pos.setZero(); if (data.FencedRegions().patches[i].Loops().size() == 0) continue; auto& e = data.FencedRegions().patches[i].Loops()[0].Edges()[0]; auto p = data.Mesh().point(data.Mesh().from_vertex_handle(e.edge)); pos.x() = p[0]; pos.y() = p[1]; pos.z() = p[2]; pos.w() = 1; DrawTextIfNotHidden(pos, mvp, std::to_string(i), tolerance); } } if (drawMotorcycleIndices) { auto& motorcycles = data.Motorcycles()->Motorcycles(); for (int i = 0; i < motorcycles.size(); ++i) { if (motorcycles[i].Path().size() == 0) continue; auto h = motorcycles[i].Path()[motorcycles[i].Path().size() / 2]; Eigen::Vector4f pos; pos.setZero(); auto p = data.Mesh().point(data.Mesh().from_vertex_handle(h)); if (motorcycles[i].Path().size() % 2 == 1) p = 0.6f * p + 0.4f * data.Mesh().point(data.Mesh().to_vertex_handle(h)); pos.x() = p[0]; pos.y() = p[1]; pos.z() = p[2]; pos.w() = 1; DrawTextIfNotHidden(pos, mvp, std::to_string(i), tolerance); } } nvgEndFrame(mNVGContext); } }