Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://github.com/NSchertler/GeneralizedMotorcycleGraph
23 June 2024, 01:59:38 UTC
  • Code
  • Branches (4)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • refs/heads/deploy-linux
    • refs/heads/deploy-osx
    • refs/heads/deploy-windows
    • refs/heads/master
    No releases to show
  • 505fc29
  • /
  • src
  • /
  • gui
  • /
  • Viewer.cpp
Raw File Download Save again
Take a new snapshot of a software origin

If the archived software origin currently browsed is not synchronized with its upstream version (for instance when new commits have been issued), you can explicitly request Software Heritage to take a new snapshot of it.

Use the form below to proceed. Once a request has been submitted and accepted, it will be processed as soon as possible. You can then check its processing state by visiting this dedicated page.
swh spinner

Processing "take a new snapshot" request ...

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge
swh:1:cnt:ef10c5f70489e60e369d71dcb5d81cfd2a4ee51a
origin badgedirectory badge
swh:1:dir:1750fa0e55bc91be2059292bf8a60fe4cc30e1f0
origin badgerevision badge
swh:1:rev:a34738fe34a051760b4042dc9d740231e511fec1
origin badgesnapshot badge
swh:1:snp:a981ea1718c19c4d9cde9d807965fd6d38bebcd2

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: a34738fe34a051760b4042dc9d740231e511fec1 authored by Nico Schertler on 31 October 2020, 07:04:57 UTC
Updated access token
Tip revision: a34738f
Viewer.cpp
#include "gui/Viewer.h"
#include "gui/ShaderPool.h"

#include <nanogui/window.h>
#include <nanogui/layout.h>
#include <nanogui/button.h>
#include <nanogui/slider.h>
#include <nanogui/label.h>
#include <nanogui/textbox.h>
#include <nanogui/checkbox.h>
#include <nanogui/combobox.h>
#include <nanogui/messagedialog.h>

#include <nsessentials/data/FileHelper.h>

#include <nsessentials/util/TimedBlock.h>
#include <nsessentials/util/UnionFind.h>
#include <iostream>
#include <set>
#include <deque>
#include <queue>
#include <utility>
#include <fstream>

#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<float, float>& 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<float, float>& 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<float, float>& 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<float>::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<unsigned char> 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<std::string> files;
	nse::data::files_in_dir(directory, files);
	
	//for (parametrizationErrorThreshold = 30; parametrizationErrorThreshold <= 90; parametrizationErrorThreshold += 10)
	{
		std::vector<std::string> modelsWithNonRectangles;
		std::vector<std::string> modelsWithFailedTracing;
		std::vector<std::string> 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 <typename EmitTriangleFunctor> 
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<EmitTriangleFunctor>(emitTriangle)(triangle);
		}
	}
}

void Viewer::UploadMeshFaces()
{
	std::vector<unsigned int> 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<uint8_t*>(indices.data()));
	meshIndices.bind();
}

void Viewer::UploadWireframeToGPU()
{
	nse::util::TimedBlock b("Uploading wireframe to GPU ..");
	std::vector<unsigned int> 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<uint8_t*>(indices.data()));
	wireframeVAO.unbind();
}

void Viewer::UploadSingularities()
{
	std::vector<Eigen::Vector4f> 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<true>(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 <typename Iterator>
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<unsigned int, 2, Eigen::Dynamic> indices[3];
	std::vector<unsigned int> 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<uint8_t*>(faceIndices[0].data()));
	fencedRegionsFaceVAO.unbind();
}

void Viewer::UploadPatches()
{
	nse::util::TimedBlock b("Uploading patches ..");

	data.FindPatchColors();

	std::vector<Eigen::Vector4f> positions;
	std::vector<Eigen::Vector4f> colors;
	std::vector<Eigen::Vector2f> 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<unsigned int> 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<size_t>& brokenArcs)
{
	brokenArcsIndexCount = 0;
	
	std::vector<unsigned int> 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);
	}
}

back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API