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
  • /
  • common.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:b672e183bffd61df9c85818e6c5207aad7de87eb
origin badgedirectory badge
swh:1:dir:6b077e51a4e0137699fb0df51458390d621719a6
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
common.cpp
#include "common.h"
#include <nsessentials/util/TimedBlock.h>

#include <map>
#include <set>
#include <Eigen/Geometry>
#include <iostream>

int FaceValence(OpenMesh::VertexHandle v, const HEMesh& mesh)
{
	int valence = 0;
	for (auto f : mesh.vf_range(v))
		++valence;
	return valence;
}

bool IsToVertexSingularity(HEMesh::HalfedgeHandle h, const HEMesh& mesh)
{
	auto v = mesh.to_vertex_handle(h);

	if (mesh.is_manifold(v))
		return IsSingularityManifoldVertex(v, mesh);
	else
	{
		CirculateBackwardUntil<true>(h, mesh, [](HEMesh::HalfedgeHandle) { return false; });
		return IsSingularityNonManifoldVertex(h, mesh);
	}
}

bool IsSingularityManifoldVertex(OpenMesh::VertexHandle v, const HEMesh& mesh, int& valenceDefect)
{
	int faceValence = FaceValence(v, mesh);
	if (mesh.is_boundary(v))
	{
		valenceDefect = faceValence - 2;
		//all incident faces must be quads
		//for(auto f : mesh.vf_range(v))
		//	if(mesh.valence(f) != 4)
		//		return true;
		if (valenceDefect != 0)
			return true;
	}
	else
	{		
		valenceDefect = faceValence - 4;
		if (faceValence != 4)
			return true;
	}
	
	//check geometry
	//const float angleVarianceThreshold = 0.2f;
	//float n = 0;
	//float mean = 0;
	//float summedVariance = 0;
	//for (auto h : mesh.vih_range(v))
	//{
	//	if (mesh.is_boundary(h))
	//		continue;
	//	auto angle = mesh.calc_sector_angle(h);
	//	++n;
	//	auto newMean = mean + 1.0f / n * (angle - mean);
	//	summedVariance += (angle - mean) * (angle - newMean);
	//	mean = newMean;
	//}
	//if (summedVariance / n > angleVarianceThreshold)
	//	return true;

	return false;
}

bool IsSingularityManifoldVertex(OpenMesh::VertexHandle v, const HEMesh& mesh)
{
	int defect;
	return IsSingularityManifoldVertex(v, mesh, defect);
}

bool IsSingularityNonManifoldVertex(HEMesh::HalfedgeHandle contextOutgoingBoundary, const HEMesh& mesh, int& valenceDefect)
{
	auto h = mesh.opposite_halfedge_handle(contextOutgoingBoundary);
	int faces = 0;
	while (!mesh.is_boundary(h))
	{
		++faces;
		h = mesh.opposite_halfedge_handle(mesh.next_halfedge_handle(h));
	}
	valenceDefect = faces - 2;
	return valenceDefect != 0;
}

bool IsSingularityNonManifoldVertex(HEMesh::HalfedgeHandle contextOutgoingBoundary, const HEMesh& mesh)
{
	int defect;
	return IsSingularityNonManifoldVertex(contextOutgoingBoundary, mesh, defect);
}

size_t GetEdgeId(unsigned int v1, unsigned int v2, std::map<std::pair<unsigned int, unsigned int>, size_t>& vertexPairToEdge, size_t& nextEdgeId)
{
	auto lo = (v1 < v2 ? v1 : v2);
	auto hi = (v1 < v2 ? v2 : v1);
	auto inserted = vertexPairToEdge.insert(std::make_pair(std::make_pair(lo, hi), nextEdgeId));
	if (inserted.second)
	{
		++nextEdgeId;
		return nextEdgeId - 1;
	}
	else
		return inserted.first->second;
};

void CatmullClarkSubdivide(FaceList& F, Matrix3Xf& V, std::vector<std::vector<unsigned int>>& subdivisionInfo)
{
	nse::util::TimedBlock b("Catmull-Clark subdivision ..");

	size_t nextEdgeId = 0;
	std::map<std::pair<unsigned int, unsigned int>, size_t> vertexPairToEdge;	

	subdivisionInfo.resize(F.size());

	//establish the edges
	size_t totalFaceValence = 0;
	for (auto& f : F)
	{
		totalFaceValence += f.size();
		for (int i = 0; i < f.size() - 1; ++i)
			GetEdgeId(f[i], f[i + 1], vertexPairToEdge, nextEdgeId);
		GetEdgeId(f.back(), f[0], vertexPairToEdge, nextEdgeId);
	}

	size_t oldVertexCount = V.cols();
	V.conservativeResize(Eigen::NoChange, oldVertexCount + vertexPairToEdge.size() + F.size());
	FaceList newF(totalFaceValence);

	//Calculate vertices for edges
	for (auto& edgeEntry : vertexPairToEdge)
	{
		size_t index = edgeEntry.second;
		unsigned int v1 = edgeEntry.first.first;
		unsigned int v2 = edgeEntry.first.second;
		V.col(oldVertexCount + index) = 0.5f * (V.col(v1) + V.col(v2));
	}

	size_t nextFaceId = 0;
	//Calculate vertices for faces; calculate new faces
	for (int iFace = 0; iFace < F.size(); ++iFace)
	{
		auto& f = F.at(iFace);
		Eigen::Vector3f centroid; centroid.setZero();
		for (int i = 0; i < f.size(); ++i)
		{
			int next = (i + 1) % f.size();
			int prev = (i - 1 + f.size()) % f.size();
			centroid += V.col(f[i]);
			subdivisionInfo[iFace].push_back(nextFaceId);
			newF[nextFaceId].resize(4);
			newF[nextFaceId][0] = GetEdgeId(f[prev], f[i], vertexPairToEdge, nextEdgeId) + oldVertexCount;
			newF[nextFaceId][1] = f[i];
			newF[nextFaceId][2] = GetEdgeId(f[i], f[next], vertexPairToEdge, nextEdgeId) + oldVertexCount;
			newF[nextFaceId][3] = oldVertexCount + vertexPairToEdge.size() + iFace;
			++nextFaceId;
		}
		centroid *= 1.0f / f.size();
		V.col(oldVertexCount + vertexPairToEdge.size() + iFace) = centroid;
	}

	F = std::move(newF);
}

void MergeTriangulatedQuads(FaceList& F, const Matrix3Xf& V, float cosAngleThreshold)
{
	nse::util::TimedBlock b("Merging triangulated quads ..");

	size_t nextEdgeId = 0;
	std::map<std::pair<unsigned int, unsigned int>, size_t> vertexPairToEdge;
	std::map<size_t, std::vector<size_t>> edgeTriangleIncidences;

	//establish edge-face incidences
	for (int iFace = 0; iFace < F.size(); ++iFace)
	{
		auto& f = F.at(iFace);
		for (int i = 0; i < f.size(); ++i)
		{
			auto next = (i + 1) % f.size();
			auto edgeId = GetEdgeId(f[i], f[next], vertexPairToEdge, nextEdgeId);
			if(f.size() == 3)
				edgeTriangleIncidences[edgeId].push_back(iFace);
		}
	}

	for (auto& entry : edgeTriangleIncidences)
	{
		if (entry.second.size() == 2) //TODO: support non-manifold edges
		{
			auto& f1 = F[entry.second[0]];
			auto& f2 = F[entry.second[1]];

			if (f1.size() != 3 || f2.size() != 3)
				continue; //one of the faces has already been merged

			unsigned int f1DiagonalStartVertex, f2DiagonalStartVertex;
			for (int i = 0; i < 3; ++i)
			{
				auto next = (i + 1) % 3;
				if (GetEdgeId(f1[i], f1[next], vertexPairToEdge, nextEdgeId) == entry.first)
					f1DiagonalStartVertex = i;
				if (GetEdgeId(f2[i], f2[next], vertexPairToEdge, nextEdgeId) == entry.first)
					f2DiagonalStartVertex = i;
			}
			unsigned int quadV[] = {
				f1[(f1DiagonalStartVertex + 2) % 3],
				f1[f1DiagonalStartVertex],
				f2[(f2DiagonalStartVertex + 2) % 3],
				f2[f2DiagonalStartVertex] };

			if (f1[f1DiagonalStartVertex] == f2[f2DiagonalStartVertex])
				continue; //The faces are not oriented consistently

			//check the angles
			bool isQuad = true;
			for (int i = 0; i < 4; ++i)
			{
				auto next = (i + 1) % 4;
				auto prev = (i + 3) % 4;
				auto dir1 = (V.col(quadV[prev]) - V.col(quadV[i])).normalized();
				auto dir2 = (V.col(quadV[next]) - V.col(quadV[i])).normalized();
				float dot = std::abs(dir1.dot(dir2));
				if (1 - dot * dot < cosAngleThreshold * cosAngleThreshold)
					isQuad = false;
			}

			if (isQuad)
			{
				f1.resize(4);
				for (int i = 0; i < 4; ++i)
					f1[i] = quadV[i];
				f2.resize(0);
			}
		}
	}

	//delete empty faces
	size_t deleted = 0;
	for (int i = F.size() - 1; i >= 0; --i)
	{
		if (F[i].size() == 0)
		{
			F.erase(F.begin() + i);
			++deleted;
		}
	}	

	std::cout << "Deleted " << deleted << " triangles." << std::endl;
}

void ProjectToTangentSpace(OpenMesh::Vec3f& vec, const OpenMesh::Vec3f& normal)
{
	if (!std::isnan(normal[0]))
		vec -= OpenMesh::dot(vec, normal) * normal;
}

float ContinuationScoreToBoundary(const HEMesh& mesh, HEMesh::HalfedgeHandle edge, const OpenMesh::Vec3f vertexNormal)
{
	auto currentVertex = mesh.to_vertex_handle(edge);

	//find the two boundary edges
	HEMesh::HalfedgeHandle incomingBoundary, outgoingBoundary;
	auto edgeVector = mesh.calc_edge_vector(edge).normalized();

	CirculateForwardUntil<false>(edge, mesh, [&](HEMesh::HalfedgeHandle h)
	{		
		if (mesh.is_boundary(h))
			outgoingBoundary = h;
		if (mesh.is_boundary(mesh.opposite_halfedge_handle(h)))
			incomingBoundary = h;

		return false;
	});

	if (incomingBoundary.is_valid())
	{
		//check continuation to boundary
		auto dIncomingOM = mesh.calc_edge_vector(incomingBoundary); ProjectToTangentSpace(dIncomingOM, vertexNormal);
		auto dOutgoingOM = mesh.calc_edge_vector(outgoingBoundary); ProjectToTangentSpace(dOutgoingOM, vertexNormal);
		dIncomingOM.normalize();
		dOutgoingOM.normalize();

		Eigen::Vector3f dIncoming(dIncomingOM[0], dIncomingOM[1], dIncomingOM[2]);
		Eigen::Vector3f dOutgoing(dOutgoingOM[0], dOutgoingOM[1], dOutgoingOM[2]);
		Eigen::Vector3f normal(vertexNormal[0], vertexNormal[1], vertexNormal[2]);

		//calculate the signed angle between the incoming and outgoing boundary edges
		float angle = std::atan2(dOutgoing.cross(dIncoming).dot(normal), dIncoming.dot(dOutgoing));
		Eigen::AngleAxis<float> rotation(angle / 2, normal);

		//calculate the virtual continuation as the angle bisector
		auto directionToBoundary = rotation * dOutgoing;

		//calculate the dot product
		return edgeVector[0] * directionToBoundary.x() + edgeVector[1] * directionToBoundary.y() + edgeVector[2] * directionToBoundary.z();
	}
	else
		return -std::numeric_limits<float>::infinity();
}

void FindPossibleContinuations(const HEMesh& mesh, HEMesh::HalfedgeHandle edge, std::vector<PathContinuation>& outContinuations, const float minCosDeviationAngle, bool allowTurnsOnRegularVertices)
{
	auto currentVertex = mesh.to_vertex_handle(edge);
	outContinuations.clear();

	OpenMesh::Vec3f vertexNormal;
	CalcVertexNormalAngleWeights(mesh, currentVertex, vertexNormal);	

	auto dir1 = mesh.calc_edge_vector(edge);
	ProjectToTangentSpace(dir1, vertexNormal); dir1.normalize();

	//outSide is true if h1 comes before h2 in an ordering of the edges around vertexNormal (having angle less than 180°)
	auto calcDot = [&](HEMesh::HalfedgeHandle h2)
	{
		auto dir2 = mesh.calc_edge_vector(h2);
		ProjectToTangentSpace(dir2, vertexNormal);
		dir2.normalize();

		return OpenMesh::dot(dir1, dir2);
	};

	bool isSingularity = false;
	if (mesh.is_manifold(currentVertex))
		isSingularity = IsSingularityManifoldVertex(currentVertex, mesh);
	else
	{
		auto contextEdge = edge;
		CirculateBackwardUntil<true>(contextEdge, mesh, [](HEMesh::HalfedgeHandle) {return false; });
		isSingularity = IsSingularityNonManifoldVertex(contextEdge, mesh);
	}

	bool madeCycle = false;
	if (allowTurnsOnRegularVertices || isSingularity)
	{
		//irregular vertex or we want to allow turns everywhere
		//find the edges that have the allowed deviation from being straight

		auto h = edge;
		CirculateForwardUntil<false>(h, mesh, [&](HEMesh::HalfedgeHandle h)
		{
			float dot = calcDot(h);

			if (dot >= minCosDeviationAngle)
				outContinuations.emplace_back(h, dot);

			return false;
		});	

		auto toBoundaryScore = ContinuationScoreToBoundary(mesh, edge, vertexNormal);
		if (!std::isinf(toBoundaryScore) && toBoundaryScore > minCosDeviationAngle)
		{
			outContinuations.emplace_back(HEMesh::HalfedgeHandle(), toBoundaryScore);
		}		
	}
	else
	{
		//continuation over a regular vertex

		HEMesh::HalfedgeHandle h;
		if (RegularContinuation(mesh, edge, h))
		{
			auto dot = calcDot(h);

			outContinuations.emplace_back(h, dot);
		}
		else
		{
			auto toBoundaryScore = ContinuationScoreToBoundary(mesh, edge, vertexNormal);
			outContinuations.emplace_back(HEMesh::HalfedgeHandle(-1), toBoundaryScore);
		}
	}
}

bool FindBestContinuation(const HEMesh& mesh, HEMesh::HalfedgeHandle edge, PathContinuation& outContinuation, const float minCosDeviationAngle, bool allowTurnsOnRegularVertices)
{
	//find the continuation with the best score

	std::vector<PathContinuation> continuations;
	FindPossibleContinuations(mesh, edge, continuations, minCosDeviationAngle, allowTurnsOnRegularVertices);
	int bestContinuation = -1;
	float bestScore = -std::numeric_limits<float>::infinity();
	for (int i = 0; i < continuations.size(); ++i)
		if (continuations[i].score > bestScore)
		{
			bestContinuation = i;
			bestScore = continuations[i].score;
		}
	if (bestContinuation == -1)
		return false;
	outContinuation = continuations[bestContinuation];
	return true;
}

bool RegularContinuation(const HEMesh& mesh, HEMesh::HalfedgeHandle edge, HEMesh::HalfedgeHandle& outContinuation)
{
	auto currentVertex = mesh.to_vertex_handle(edge);

	if (!mesh.is_boundary(currentVertex))
	{
		//regular non-boundary
		outContinuation = mesh.next_halfedge_handle(mesh.opposite_halfedge_handle(mesh.next_halfedge_handle(edge)));
		return true;
	}
	else
	{
		//regular boundary
		if (mesh.is_boundary(edge))
		{
			auto h = edge;
			CirculateBackwardUntil<true>(h, mesh, [](HEMesh::HalfedgeHandle) {return false; });
			outContinuation = h;
			return true;
		}
		else
		{
			auto nextOpposite = mesh.opposite_halfedge_handle(mesh.next_halfedge_handle(edge));
			if (!mesh.is_boundary(nextOpposite))
			{
				outContinuation = mesh.next_halfedge_handle(nextOpposite);
				return true;
			}
			else
				return false;
		}
	}
}

void PrintHalfedge(std::ostream& stream, HEMesh::HalfedgeHandle h, const HEMesh& mesh)
{
	stream << "(" << mesh.from_vertex_handle(h) << ") -- " << h << " --> ";
}

void PrintFullHalfedge(std::ostream& stream, HEMesh::HalfedgeHandle h, const HEMesh& mesh)
{
	stream << "(" << mesh.from_vertex_handle(h) << ") -- " << h << " --> (" << mesh.to_vertex_handle(h) << ")";
}

int TurnsBetweenEdges(HEMesh::HalfedgeHandle h1, HEMesh::HalfedgeHandle h2, const HEMesh& mesh, bool useMargin)
{
	if (useMargin && mesh.is_boundary(mesh.opposite_halfedge_handle(h1)) && mesh.is_boundary(mesh.opposite_halfedge_handle(h2)))
		return 0; //add regular margin around mesh

	//Circulate h1 until it is equal to h2 and count the hops
	int hops = 1;
	h1 = mesh.next_halfedge_handle(h1);
	while (h1 != h2)
	{
		h1 = mesh.opposite_halfedge_handle(h1);
		if (mesh.is_boundary(h1))
			++hops;
		h1 = mesh.next_halfedge_handle(h1);
		++hops;
	}
	return 2 - hops;
}

void CalcVertexNormalAngleWeights(const HEMesh& mesh, OpenMesh::VertexHandle _vh, OpenMesh::Vec3f& _n)
{
	//adaptation of OpenMesh::calc_vertex_normal_correct

	_n.vectorize(0.0);
	HEMesh::ConstVertexIHalfedgeIter cvih_it = mesh.cvih_iter(_vh);
	if (!cvih_it.is_valid())
	{//don't crash on isolated vertices
		return;
	}
	OpenMesh::Vec3f in_he_vec;
	mesh.calc_edge_vector(*cvih_it, in_he_vec);
	for (; cvih_it.is_valid(); ++cvih_it)
	{//calculates the sector normal defined by cvih_it and adds it to _n
		if (mesh.is_boundary(*cvih_it))
		{
			continue;
		}
		OpenMesh::HalfedgeHandle out_heh(mesh.next_halfedge_handle(*cvih_it));
		OpenMesh::Vec3f out_he_vec;
		mesh.calc_edge_vector(out_heh, out_he_vec);
		auto angle = acos(OpenMesh::sane_aarg(OpenMesh::dot(in_he_vec, out_he_vec)));
		_n += angle * cross(in_he_vec, out_he_vec).normalized();
		in_he_vec = out_he_vec;
		in_he_vec *= -1;//change the orientation
	}
	_n.normalize();
}

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