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

Raw File Download

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
content badge Iframe embedding
swh:1:cnt:cd2b64905c43df03a300b58abda4916d412ed76d

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
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
#include "path_extraction.h"

#include "parameterization.h"
#include "stretch_angles.h"
#include "stripe_patterns.h"
#include "timer.h"

#include <geometrycentral/surface/direction_fields.h>
#include <geometrycentral/surface/manifold_surface_mesh.h>
#include <igl/boundary_loop.h>
#include <igl/ramer_douglas_peucker.h>
#include <polyscope/curve_network.h>
#include <polyscope/polyscope.h>

#ifdef USE_ORTOOLS
#include <ortools/constraint_solver/routing.h>
#include <ortools/constraint_solver/routing_enums.pb.h>
#include <ortools/constraint_solver/routing_index_manager.h>
#include <ortools/constraint_solver/routing_parameters.h>
#endif

#ifdef USE_PARDISO
#include <Eigen/PardisoSupport>
using LDLTSolver = Eigen::PardisoLDLT<Eigen::SparseMatrix<double>>;
using LLTSolver = Eigen::PardisoLLT<Eigen::SparseMatrix<double>>;
using LUSolver = Eigen::PardisoLU<Eigen::SparseMatrix<double>>;
#else
using LDLTSolver = Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>>;
using LLTSolver = Eigen::SimplicialLLT<Eigen::SparseMatrix<double>>;
using LUSolver = Eigen::SparseLU<Eigen::SparseMatrix<double>>;
#endif // USE_PARDISO

using namespace geometrycentral;
using namespace geometrycentral::surface;

namespace
{
#ifdef USE_ORTOOLS
std::vector<int64_t> computeReordering(const std::vector<Vector3>& endPoints, double timeLimit)
{
  using namespace operations_research;

  // Create the VRP routing model. The 1 means we are only looking for a single path.
  int nPaths = endPoints.size() / 4;
  RoutingIndexManager manager(2 * nPaths + 1, 1, RoutingIndexManager::NodeIndex{2 * nPaths});
  RoutingModel routing(manager);

  // For every path node, add a disjunction so that we do not also trace its reverse
  for(int64_t i = 0; i < nPaths; ++i)
    routing.AddDisjunction({2 * i, 2 * i + 1});

  // Wrap the distance function so that it converts to an integer, as or-tools requires.
  // Distances are rounded to the nearest nanometer value, should be precise enough
  const double COST_MULTIPLIER = 1e6;
  int64_t transitCallbackIndex = routing.RegisterTransitCallback([&](int64_t i, int64_t j) -> int64_t {
    int64_t fromNode = manager.IndexToNode(i).value();
    int64_t toNode = manager.IndexToNode(j).value();
    if(fromNode == 2 * nPaths || toNode == 2 * nPaths)
      return 0;
    double dist = (endPoints[2 * fromNode + 1] - endPoints[2 * toNode]).norm();
    return static_cast<int64_t>(COST_MULTIPLIER * dist);
  });
  routing.SetArcCostEvaluatorOfAllVehicles(transitCallbackIndex);

  RoutingSearchParameters searchParameters = DefaultRoutingSearchParameters();
  searchParameters.mutable_time_limit()->set_seconds(timeLimit);
  searchParameters.set_solution_limit(kint64max);
  searchParameters.set_first_solution_strategy(FirstSolutionStrategy::SAVINGS);
  searchParameters.set_local_search_metaheuristic(LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH);

  // solve routing problem
  auto finalAssignment = std::make_unique<Assignment>(routing.SolveWithParameters(searchParameters));

  // Iterate over the result
  std::vector<int64_t> result;
  result.reserve(nPaths);
  int64_t index = routing.Start(0);
  for(int i = 0; i < nPaths; ++i)
  {
    index = manager.IndexToNode(finalAssignment->Value(routing.NextVar(index))).value();
    assert(index <= 2 * nPaths);
    result.push_back(index);
  }

  // build polyline list from the list of endpoint indices
  return result;
}
#else
std::vector<int> greedyIndexing(const std::vector<Vector3>& endPoints, EmbeddedGeometryInterface& geometry)
{
  // find closest end-point to home
  Vector3 homePos = {-80, -80};
  double minDist = std::numeric_limits<double>::max();
  int minIdx;
  for(int j = 0; j < endPoints.size(); ++j)
  {
    if((endPoints[j] - homePos).norm() < minDist)
    {
      minDist = (endPoints[j] - homePos).norm();
      minIdx = j;
    }
  }

  // indices into endPoints paired with the direction of isoline (true = standard, false = reversed)
  std::vector<int> endPointIndices(endPoints.size() / 2);

  // tells whether isolines[i] has been visited or not
  std::vector<bool> visited(endPoints.size() / 2, false);
  visited[minIdx / 2] = true;

  // order end-point indices by proximity
  endPointIndices[0] = minIdx;
  Vector3 current;
  if(minIdx % 2 == 0)
    current = endPoints[minIdx + 1];
  else
    current = endPoints[minIdx - 1];

  for(int i = 1; i < endPoints.size() / 2; ++i)
  {
    // find closest point to current
    double minDist = std::numeric_limits<double>::max();
    int minIdx;
    for(int j = 0; j < endPoints.size(); ++j)
    {
      if(!visited[j / 2] && (current - endPoints[j]).norm() < minDist)
      {
        minDist = (current - endPoints[j]).norm();
        minIdx = j;
      }
    }
    // list index of isoline + whether it should be reversed or not
    endPointIndices[i] = minIdx;
    // set current to the other end-point
    if(minIdx % 2 == 0)
      current = endPoints[minIdx + 1];
    else
      current = endPoints[minIdx - 1];

    visited[minIdx / 2] = true;
  }

  return endPointIndices;
}
#endif
} // namespace

std::vector<std::vector<Vector3>>
orderPolylines(const std::vector<std::vector<Vector3>>& isolines, EmbeddedGeometryInterface& geometry, double timeLimit)
{
#ifdef USE_ORTOOLS
  // build list of isoline end-points, enumerated twice (one for each direction)
  std::vector<Vector3> endPoints(4 * isolines.size());

  for(size_t i = 0; i < isolines.size(); ++i)
  {
    endPoints[4 * i] = isolines[i][0];
    endPoints[4 * i + 1] = isolines[i][isolines[i].size() - 1];

    endPoints[4 * i + 2] = endPoints[4 * i + 1];
    endPoints[4 * i + 3] = endPoints[4 * i];
  }

  // TSP solving
  std::vector<int64_t> indices = computeReordering(endPoints, timeLimit);
#else
  std::vector<Vector3> endPoints(2 * isolines.size());

  for(int i = 0; i < isolines.size(); ++i)
  {
    endPoints[2 * i] = isolines[i][0];
    endPoints[2 * i + 1] = isolines[i][isolines[i].size() - 1];
  }

  std::vector<int> indices = greedyIndexing(endPoints, geometry);
#endif
  // assemble final polyline list
  std::vector<std::vector<Vector3>> polylines(isolines.size());
  for(size_t i = 0; i < isolines.size(); ++i)
  {
    auto isoline = isolines.at(indices[i] / 2);

    if(indices[i] % 2 == 0)
    {
      polylines[i] = isoline;
    }
    else // enumerate vertices from last to first
    {
      polylines[i].reserve(isoline.size());
      for(int j = isoline.size() - 1; j >= 0; --j)
      {
        polylines[i].push_back(isoline[j]);
      }
    }
  }

  return polylines;
}

std::vector<std::vector<Vector3>> simplifyPolylines(const std::vector<std::vector<Vector3>>& polylines, double z)
{
  std::vector<std::vector<Vector3>> simplified;

  for(auto& polyline: polylines)
  {
    // convert to Eigen
    Eigen::MatrixXd P(polyline.size(), 2);
    for(size_t i = 0; i < polyline.size(); ++i)
      P.row(i) << polyline[i].x, polyline[i].y;

    // simplify polyline (RDP)
    Eigen::MatrixXd S;
    Eigen::VectorXi J;
    igl::ramer_douglas_peucker(P, 1e-2, S, J);

    // convert back to geometrycentral
    std::vector<Vector3> simplifiedPolyline(S.rows());
    for(int i = 0; i < S.rows(); ++i)
      simplifiedPolyline[i] = Vector3{S(i, 0), S(i, 1), z};

    simplified.push_back(simplifiedPolyline);
  }
  return simplified;
}

void writePaths(const std::string& filename, const std::vector<std::vector<Vector3>>& paths, double height)
{
  std::ofstream s(filename, std::ios_base::app);
  for(const auto& path: paths)
  {
    s << path.size() << "\n";
    for(Vector3 p: path)
      s << p.x << " " << p.y << " " << height << "\n";
  }
}

void drawPathsAndTravels(const std::vector<std::vector<Vector3>>& polylines, double spacing, int id)
{
  std::vector<Vector3> nodes;
  std::vector<std::array<size_t, 2>> edges;
  std::vector<double> colors;

  double c = 0;
  size_t k = 0;
  for(auto& polyline: polylines)
  {
    nodes.insert(nodes.end(), polyline.begin(), polyline.end());
    colors.insert(colors.end(), polyline.size() - 1, c);
    c += 1;

    for(size_t j = 0; j < polyline.size() - 1; ++j)
    {
      edges.push_back({k + j, k + j + 1});
    }
    k += polyline.size();
  }

  std::vector<Vector3> nodes2;
  std::vector<std::array<size_t, 2>> edges2;
  double distance = 0;
  for(size_t i = 0; i < polylines.size() - 1; ++i)
  {
    nodes2.push_back(polylines[i][polylines[i].size() - 1]);
    nodes2.push_back(polylines[i + 1][0]);
    edges2.push_back({2 * i, 2 * i + 1});
    distance += norm(polylines[i][polylines[i].size() - 1] - polylines[i + 1][0]);
  }
  std::cout << "Layer " << id << ": total travel distance = " << distance << "mm.\n";

  polyscope::registerCurveNetwork("Layer " + std::to_string(id), nodes, edges)->setRadius(spacing / 2, false);
  polyscope::getCurveNetwork("Layer " + std::to_string(id))
      ->addEdgeScalarQuantity("order", colors)
      ->setColorMap("blues")
      ->setEnabled(true);

  polyscope::registerCurveNetwork("Travel " + std::to_string(id), nodes2, edges2)
      ->setRadius(spacing / 2, false)
      ->setEnabled(false);
}

void stripePattern(VertexPositionGeometry& geometry,
                   const Eigen::MatrixXd& _V,
                   Eigen::MatrixXd& _P,
                   const Eigen::MatrixXi& _F,
                   VertexData<double>& vTheta2,
                   std::string filename,
                   double timeLimit,
                   double layerHeight,
                   double spacing,
                   int nLayers)
{
  Eigen::MatrixXd V = _V;
  Eigen::MatrixXi F = _F;
  std::vector<Eigen::SparseMatrix<double>> subdivMat;

  subdivideMesh(geometry, V, _P, F, subdivMat, spacing);

  ManifoldSurfaceMesh subdividedMesh(F);
  Eigen::MatrixXd P_3D(_P.rows(), 3);
  P_3D.leftCols(2) = _P;
  P_3D.col(2).setZero();
  VertexPositionGeometry geometryUV(subdividedMesh, P_3D);

  FaceData<double> theta1 = computeStretchAngles(subdividedMesh, V, _P, F);

  // convert face angles into vertex angles
  Eigen::VectorXd th1(V.rows());
  for(int i = 0; i < V.rows(); ++i)
  {
    double sumAngles = 0;
    int nFaces = 0;
    Vertex v = subdividedMesh.vertex(i);
    for(Face f: v.adjacentFaces())
    {
      // add face orientations in global coordinates
      if(!f.isBoundaryLoop())
      {
        sumAngles += theta1[f];
        nFaces += 1;
      }
    }
    th1(i) = sumAngles / nFaces;
  }

  Eigen::VectorXd th2 = vTheta2.toVector();
  for(auto& mat: subdivMat)
    th2 = mat * th2;

  std::vector<std::vector<std::vector<Vector3>>> paths =
      generatePaths(geometryUV, th1, th2, layerHeight, nLayers, spacing, timeLimit);

  for(int i = 0; i < nLayers; ++i)
  {
    writePaths(filename + ".path", paths[i], (i + 1) * layerHeight);
    drawPathsAndTravels(paths[i], spacing, i + 1);
  }
}

std::vector<std::vector<std::vector<geometrycentral::Vector3>>> generatePaths(EmbeddedGeometryInterface& geometry,
                                                                              const Eigen::VectorXd& theta1,
                                                                              const Eigen::VectorXd& theta2,
                                                                              double layerHeight,
                                                                              int nLayers,
                                                                              double spacing,
                                                                              double timeLimit)
{
  std::vector<std::vector<std::vector<Vector3>>> paths;

  LDLTSolver solver;

  SparseMatrix<double> massMatrix = computeRealVertexMassMatrix(geometry);
  SurfaceMesh& mesh = geometry.mesh;
  VertexData<double> frequencies(mesh, 1.0 / spacing);

  Vector<double> u = Vector<double>::Random(mesh.nVertices() * 2);
  for(int i = 0; i < nLayers / 2; ++i)
  {
    Timer timer("Layers " + std::to_string(2 * i) + " and " + std::to_string(2 * i + 1));
    geometry.requireVertexTangentBasis();

    // compute direction field
    VertexData<Vector2> directionField(mesh);
    for(size_t j = 0; j < mesh.nVertices(); ++j)
    {
      // interpolate orientations w.r.t layer
      directionField[j] = Vector2::fromAngle(2 * (theta1(j) + (i / (nLayers / 2 - 1.) - 1 / 2.) * theta2(j)));
      // express vectors in their respective vertex local bases (the stripes algorithm expects that)
      auto basisVector = geometry.vertexTangentBasis[j][0];
      Vector2 base{basisVector.x, basisVector.y};
      directionField[j] = -directionField[j] / base.pow(2);
    }

    // build matrices and factorize solver
    FaceData<int> fieldIndices = computeFaceIndex(geometry, directionField, 2);
    SparseMatrix<double> energyMatrix =
        buildVertexEnergyMatrix(geometry, directionField, fieldIndices, 2 * PI * frequencies);
    if(i == 0)
      solver.compute(energyMatrix);
    else
      solver.factorize(energyMatrix);

    // solve the eigenvalue problem
    Vector<double> x = u;
    double residual = eigenvectorResidual(energyMatrix, massMatrix, x);
    const double tol = 1e-5;
    while(residual > tol)
    {
      // Solve
      Vector<double> rhs = massMatrix * u;
      x = solver.solve(rhs);

      // Re-normalize
      double scale = std::sqrt(std::abs(x.dot(massMatrix * x)));
      x /= scale;

      // Update
      u = x;
      residual = eigenvectorResidual(energyMatrix, massMatrix, x);
    }

    // Copy the result to a VertexData vector
    VertexData<Vector2> parameterization(mesh);
    for(size_t j = 0; j < mesh.nVertices(); ++j)
    {
      parameterization[j].x = u(2 * j);
      parameterization[j].y = u(2 * j + 1);
    }

    CornerData<double> stripeValues;
    FaceData<int> stripeIndices;
    std::tie(stripeValues, stripeIndices) =
        computeTextureCoordinates(geometry, directionField, 2 * PI * frequencies, parameterization);

    // extract isolines
    for(int j = 0; j < 2; ++j)
    {
      const auto& [points, edges] = extractPolylinesFromStripePattern(geometry, stripeValues + j * PI, stripeIndices,
                                                                      fieldIndices, directionField);
      auto polylines = edgeToPolyline(points, edges);
      polylines = orderPolylines(polylines, geometry, timeLimit);
      paths.push_back(simplifyPolylines(polylines, (2 * i + j) * layerHeight));
    }
  }

  return paths;
}

std::vector<std::vector<Vector3>> edgeToPolyline(const std::vector<Vector3>& points,
                                                 const std::vector<std::array<size_t, 2>>& edges)
{
  // build up connectivity information (neighoring indices on polyline)
  std::vector<std::vector<size_t>> connectivity(points.size());

  for(auto& edge: edges)
  {
    connectivity[edge[0]].push_back(edge[1]);
    connectivity[edge[1]].push_back(edge[0]);
  }

  std::vector<std::vector<Vector3>> polylines;
  std::vector<bool> visited(points.size(), false);
  for(size_t i = 0; i < points.size(); ++i)
    if(!visited[i])
    {
      std::vector<Vector3> isoline;

      size_t nbOfPieces = connectivity[i].size();
      if(nbOfPieces == 0)
        continue; // ignore isolated points

      size_t currIdx = connectivity[i][0];
      size_t prevIdx = i;
      bool open = true;
      if(nbOfPieces == 2) // walk to the end of the line (if open)
      {
        while(connectivity[currIdx].size() == 2 && currIdx != i)
        {
          if(connectivity[currIdx][0] == prevIdx)
          {
            prevIdx = currIdx;
            currIdx = connectivity[currIdx][1];
          }
          else
          {
            prevIdx = currIdx;
            currIdx = connectivity[currIdx][0];
          }
        }
        open = currIdx != i;
        std::swap(prevIdx, currIdx); // revert the order to start from this end
      }

      isoline.push_back(points[prevIdx]);
      visited[prevIdx] = true;

      while((connectivity[currIdx].size() == 2 && open) || (currIdx != i && !open))
      {
        isoline.push_back(points[currIdx]);
        visited[currIdx] = true;

        if(connectivity[currIdx][0] == prevIdx)
        {
          prevIdx = currIdx;
          currIdx = connectivity[currIdx][1];
        }
        else
        {
          prevIdx = currIdx;
          currIdx = connectivity[currIdx][0];
        }
      }
      assert(connectivity[currIdx].size() == 1 || currIdx == i && !open);

      isoline.push_back(points[currIdx]);
      visited[currIdx] = true;

      polylines.push_back(isoline);
    }
  return polylines;
}

back to top

Software Heritage — Copyright (C) 2015–2025, 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