ParametricObjectsDemo

VTKExamples/Cxx/GeometricObjects/ParametricObjectsDemo


Description

Demonstrates the Parametric classes added by Andrew Maclean and additional classes added by Tim Meehan. The parametric spline is also included.

Options are provided to:

  • Specify a single surface (-s SURFACE_NAME)
  • Save the image (-w)
  • Color the back-face (-b)
  • Add normals (-n)

For example:

ParametricSurfaces -s RandomHills -w -b -n

Will write out a file called RandomHills.png and produce an image with all the options enabled.

ParametricSurfaces -w

Will write out a file with no other options enabled called ParametricObjectsDemo.png.

Note

To really appreciate the complexity of some of these surfaces, select a single surface, and use the options -b -n. Also try specifying wireframe ( toggle "w" on the keyboard) and zooming in and out.

Tip

If you color the back face, the three-dimensional orientable surfaces will only show backface coloring inside the surface e.g ConicSpiral or Torus. For three dimensional non-orientable surfaces; backface coloring is visible because of the twisting used to generate these surfaces e.g Boy or Figure8Klein.

Cite

See: Parametric Equations for Surfaces, for more information. This paper gives a description of the first fifteen surfaces, including their parametric equations and derivatives. Also provided is an example of how to create your own surface, namely the Figure-8 Torus.

Other Languages

See (Python), (CSharp)

Code

ParametricObjectsDemo.cxx

#include <vtkActor.h>
#include <vtkActor2D.h>
#include <vtkCamera.h>
#include <vtkMath.h>
#include <vtkMinimalStandardRandomSequence.h>
#include <vtkNamedColors.h>
#include <vtkParametricFunctionSource.h>
#include <vtkPoints.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkTextMapper.h>
#include <vtkTextProperty.h>

#include <vtkParametricBoy.h>
#include <vtkParametricConicSpiral.h>
#include <vtkParametricCrossCap.h>
#include <vtkParametricDini.h>
#include <vtkParametricEllipsoid.h>
#include <vtkParametricEnneper.h>
#include <vtkParametricFigure8Klein.h>
#include <vtkParametricKlein.h>
#include <vtkParametricMobius.h>
#include <vtkParametricRandomHills.h>
#include <vtkParametricRoman.h>
#include <vtkParametricSpline.h>
#include <vtkParametricSuperEllipsoid.h>
#include <vtkParametricSuperToroid.h>
#include <vtkParametricTorus.h>
// Extra parametric surfaces.
#include <vtkParametricBohemianDome.h>
#include <vtkParametricBour.h>
#include <vtkParametricCatalanMinimal.h>
#include <vtkParametricHenneberg.h>
#include <vtkParametricKuen.h>
#include <vtkParametricPluckerConoid.h>
#include <vtkParametricPseudosphere.h>

// For glyphing
#include <vtkArrowSource.h>
#include <vtkDataSet.h>
#include <vtkGlyph3D.h>
#include <vtkMaskPoints.h>

// For writing out the image.
#include <vtkBMPWriter.h>
#include <vtkImageWriter.h>
#include <vtkJPEGWriter.h>
#include <vtkPNGWriter.h>
#include <vtkPNMWriter.h>
#include <vtkPostScriptWriter.h>
#include <vtkTIFFWriter.h>
#include <vtkWindowToImageFilter.h>

#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <locale>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>

namespace {

// Holds the arguments from the command line as a map.
// e.g. key = name; value = (true|false,[value_0 ... value_n])
typedef std::map<std::string, std::pair<bool, std::vector<std::string>>>
    TCmdArgs;

class CommandLineParser
{
public:
  /**
   * @param argvArgs: the command line arguments as a vector of strings.
   * @param optArgs: the non-positional keys and their value.
   * @param optArgsParam: the non-positional keys that have parameters and their
   * value.
   * @param posNum: the expected number of positional variables.
   * @param posNumKName: the name of the positional values.
   */
  CommandLineParser(std::vector<std::string>& argvArgs,
                    std::map<std::string, std::string>& optArgs,
                    std::map<std::string, std::string>& optArgsParam,
                    int posNum = 0, std::string const& posName = "_PKN");

  ~CommandLineParser();

public:
  /**
   * Parse the parameters from the command line.
   *
   * @return true if the parse was successful, false if unsuccessful.
   */
  bool Parse();

  /**
   * Get the parsed command line arguments.
   *
   * @return The parsed arguments.
   */
  TCmdArgs GetCommandArguments()
  {
    return this->cmdArgs;
  }

  /**
   * @return The error message if Parse() failed.
   */
  std::string GetParseError()
  {
    return this->parseError;
  }

  /**
   * @return The key name used to identify positional arguments.
   */
  std::string getPositionalKeyName()
  {
    return this->posName;
  }

  /**
   * @return A string of the parsed command arguments.
   */
  std::string DisplayCommandArguments();

private:
  /**
   * Separate the key from the value, e.g -kv -> k v.
   *
   * @return The vector with the keys separated from their value(s).
   */
  std::vector<std::string> SeparateKV();

  /**
   * Find any unknown non-positional keys.
   *
   * @return If unknown keys were found, true is returned.
   */
  bool HasUnknownKeys();

  /**
   * Find any duplicate non-positional keys.
   *
   * @return If duplicate keys were found, true is returned.
   */
  bool HasDuplicateKeys();

  /**
   * Take a map and get the set of values in the map.
   *
   * @param m: The map.
   * @param s: The set of values from the map.
   */
  template <typename K, typename V>
  void GetSetOfValues(std::map<K, V> const& m, std::set<V>& s);

  /**
   * Build a map of aliases for the keys from the key/value pairs.
   *
   * @param m: A map of key, value pairs.
   * @param aliases: A map of aliases keyed by value.
   */
  template <typename K, typename V>
  void FindAliases(std::map<K, V> m,
                   std::map<V, std::pair<std::vector<K>, int>>& aliases);

private:
  TCmdArgs cmdArgs;
  std::map<std::string, std::pair<std::vector<std::string>, int>> aliases;
  std::vector<std::string> cmdLineVec;
  std::map<std::string, std::string>& optArgs;
  std::map<std::string, std::string>& optArgsParam;
  int posNum;
  std::string posName;
  std::string parseError;
  std::vector<std::string> cl;
};

/**
 * Show the command lime parameters.
 *
 * @param fn: The program name.
 */
std::string ShowUsage(std::string fn);

/**
 * Create a map of the parametric functions and set some parameters.
 * The first key groups the parametric functions and the
 *   second key is the name of the function.
 *
 * @return The map of functions.
 */
std::map<int, std::map<std::string, vtkSmartPointer<vtkParametricFunction>>>
GetParametricFunctions();

/**
 * Write the render window view to an image file.
 *
 * Image types supported are:
 *  BMP, JPEG, PNM, PNG, PostScript, TIFF.
 * The default parameters are used for all writers, change as needed.
 *
 * @param fileName The file name, if no extension then PNG is assumed.
 * @param renWin The render window.
 * @param rgba Used to set the buffer type.
 *
 */
void WriteImage(std::string const& fileName, vtkRenderWindow* renWin,
                bool rgba = true);

/**
 * Get the centre of the object from the bounding box.
 */
std::vector<double> GetCentre(const std::vector<double>& bounds);

/**
 * Calculate the maximum length of side of the bounding box.
 */
double GetMaximumLength(const std::vector<double>& bounds);

/**
 * Display the dimensions of the bounding box, maximum diagonal length
 *    and coordinates of the centre.
 *
 * @param name: The name of the object.
 * @param index: The index of the object.
 * @param bounds: The bounding box of the object.
 *
 */
void DisplayBoundingBoxAndCenter(std::string const& name, int const& index,
                                 std::vector<double> const& bounds);

} // namespace

int main(int argc, char* argv[])
{
  // These two maps need to be filled in by the user.
  // key: Optional arguments such as -f or --foo with no parameters.
  // value: A suitable name. For the keys -f or --foo, the name would be the
  // same e.g f.
  std::map<std::string, std::string> optArgs;
  // key: Optional arguments requiring one or more paramters such as -s fn or
  // --some_file fn. value: A suitable name. For the keys -s or --some_file, the
  // name would be the same e.g s. To handle non-optional arguments we use a
  // special key: _PKN (which can be user defined).
  std::map<std::string, std::string> optArgsParam;

  // Specify key/value pairs for the arguments we want.
  optArgs["-b"] = "b";
  optArgs["-n"] = "n";
  optArgs["-w"] = "w";
  // These are followed by one or more parameters on the command line.
  optArgsParam["-s"] = "s";

  // The command line arguments
  std::vector<std::string> cmdVec;
  for (auto i = 1; i < argc; ++i)
  {
    cmdVec.push_back(argv[i]);
  }

  CommandLineParser clp(cmdVec, optArgs, optArgsParam);
  if (!cmdVec.empty())
  {
    // Usually -h and --help are reserved for help.
    for (auto it : cmdVec)
    {
      if (it == "-h" || it == "--help")
      {
        std::cout << ShowUsage(argv[0]) << std::endl;
        return EXIT_SUCCESS;
      }
    }
    if (!clp.Parse())
    {
      std::cerr << clp.GetParseError() << std::endl;
      std::cerr << ShowUsage(argv[0]) << std::endl;
      return EXIT_FAILURE;
    }
  }
  TCmdArgs cmdArgs = clp.GetCommandArguments();
  // std::cout << clp.DisplayCommandArguments() << std::endl;

  std::pair<std::string, int> singleSurface;
  if (cmdArgs["s"].first)
  {
    if (cmdArgs["s"].second.size() > 0)
    {
      singleSurface.first = cmdArgs["s"].second[0];
      singleSurface.second = -1;
    }
    else
    {
      std::cerr << "Surface name is missing." << std::endl;
      return EXIT_FAILURE;
    }
  }

  auto colors = vtkSmartPointer<vtkNamedColors>::New();

  auto rendererSize = 200;
  auto gridColumnDimensions = 5;
  auto gridRowDimensions = 5;

  if (cmdArgs["s"].first)
  {
    rendererSize = 800;
    gridColumnDimensions = 1;
    gridRowDimensions = 1;
  }

  // Create one text property for all
  auto textProperty = vtkSmartPointer<vtkTextProperty>::New();
  textProperty->SetJustificationToCentered();
  textProperty->SetFontSize(rendererSize / 12);
  textProperty->SetColor(colors->GetColor3d("LavenderBlush").GetData());

  // Create a parametric function source, renderer, mapper, and actor
  // for each object
  std::vector<vtkSmartPointer<vtkParametricFunctionSource>> pfnSrcs;
  std::vector<vtkSmartPointer<vtkRenderer>> renderers;
  std::vector<vtkSmartPointer<vtkPolyDataMapper>> mappers;
  std::vector<vtkSmartPointer<vtkActor>> actors;
  std::vector<vtkSmartPointer<vtkTextMapper>> textmappers;
  std::vector<vtkSmartPointer<vtkActor2D>> textactors;

  // Glyph the normals.
  std::vector<vtkSmartPointer<vtkMaskPoints>> maskPts;
  std::vector<vtkSmartPointer<vtkArrowSource>> arrow;
  std::vector<vtkSmartPointer<vtkGlyph3D>> glyph;
  std::vector<vtkSmartPointer<vtkPolyDataMapper>> glyphMapper;
  std::vector<vtkSmartPointer<vtkActor>> glyphActor;

  auto backProperty = vtkSmartPointer<vtkProperty>::New();
  if (cmdArgs["b"].first)
  {
    backProperty->SetColor(colors->GetColor3d("Peru").GetData());
  }

  std::vector<std::vector<double>> boundingBox;

  // Get the parametric functions and build the pipeline
  auto pfn = GetParametricFunctions();

  if (cmdArgs["s"].first)
  {
    // Is the surface name in the map?
    std::vector<bool> surfaceExists;
    for (auto& t : pfn)
    {
      if (t.second.find(cmdArgs["s"].second[0]) == t.second.end())
        surfaceExists.push_back(false);
      else
        surfaceExists.push_back(true);
    }
    if (std::find(std::begin(surfaceExists), std::end(surfaceExists), true) ==
        end(surfaceExists))
    {
      // All entries in surfaceExists are false
      std::cout << "Nonexistent surface: " << cmdArgs["s"].second[0]
                << std::endl;
      return EXIT_FAILURE;
    }
  }

  // The count of parametric objects
  auto objCount = 0;
  std::vector<std::string> sortedNames;
  for (auto t : pfn)
  {
    for (auto obj : t.second)
    {
      sortedNames.push_back(obj.first);
      if (cmdArgs["s"].first)
      {
        if (obj.first == singleSurface.first)
        {
          singleSurface.second = objCount;
        }
      }
      pfnSrcs.push_back(vtkSmartPointer<vtkParametricFunctionSource>::New());
      pfnSrcs[objCount]->SetParametricFunction(pfn[t.first][obj.first]);
      pfnSrcs[objCount]->SetUResolution(51);
      pfnSrcs[objCount]->SetVResolution(51);
      pfnSrcs[objCount]->SetWResolution(51);
      pfnSrcs[objCount]->Update();

      mappers.push_back(vtkSmartPointer<vtkPolyDataMapper>::New());
      mappers[objCount]->SetInputConnection(pfnSrcs[objCount]->GetOutputPort());

      actors.push_back(vtkSmartPointer<vtkActor>::New());
      actors[objCount]->SetMapper(mappers[objCount]);
      actors[objCount]->GetProperty()->SetColor(
          colors->GetColor3d("NavajoWhite").GetData());
      if (cmdArgs["b"].first)
      {

        actors[objCount]->SetBackfaceProperty(backProperty);
      }

      textmappers.push_back(vtkSmartPointer<vtkTextMapper>::New());
      textmappers[objCount]->SetInput(obj.first.c_str());
      textmappers[objCount]->SetTextProperty(textProperty);

      textactors.push_back(vtkSmartPointer<vtkActor2D>::New());
      textactors[objCount]->SetMapper(textmappers[objCount]);
      textactors[objCount]->SetPosition(rendererSize / 2.0, 8);

      renderers.push_back(vtkSmartPointer<vtkRenderer>::New());

      double bounds[6];
      pfnSrcs[objCount]->GetOutput()->GetBounds(bounds);
      std::vector<double> v(std::begin(bounds), std::end(bounds));
      boundingBox.push_back(v);
      // DisplayBoundingBoxAndCenter(obj.first, objCount, v);

      if (cmdArgs["n"].first)
      {
        // Glyphing
        maskPts.push_back(vtkSmartPointer<vtkMaskPoints>::New());
        maskPts[objCount]->RandomModeOn();
        maskPts[objCount]->SetMaximumNumberOfPoints(150);
        maskPts[objCount]->SetInputConnection(
            pfnSrcs[objCount]->GetOutputPort());

        arrow.push_back(vtkSmartPointer<vtkArrowSource>::New());
        arrow[objCount]->SetTipResolution(16);
        arrow[objCount]->SetTipLength(0.3);
        arrow[objCount]->SetTipRadius(0.1);

        double glyphScale = GetMaximumLength(boundingBox[objCount]);

        glyph.push_back(vtkSmartPointer<vtkGlyph3D>::New());
        glyph[objCount]->SetSourceConnection(arrow[objCount]->GetOutputPort());
        glyph[objCount]->SetInputConnection(maskPts[objCount]->GetOutputPort());
        glyph[objCount]->SetVectorModeToUseNormal();
        glyph[objCount]->SetScaleFactor(glyphScale / 10.0);
        glyph[objCount]->OrientOn();
        glyph[objCount]->Update();

        glyphMapper.push_back(vtkSmartPointer<vtkPolyDataMapper>::New());
        glyphMapper[objCount]->SetInputConnection(
            glyph[objCount]->GetOutputPort());

        glyphActor.push_back(vtkSmartPointer<vtkActor>::New());
        glyphActor[objCount]->SetMapper(glyphMapper[objCount]);
        glyphActor[objCount]->GetProperty()->SetColor(
            colors->GetColor3d("GreenYellow").GetData());
      }
      objCount++;
    }
  }

  // Need a renderer even if there is no actor
  for (auto i = objCount; i < gridColumnDimensions * gridRowDimensions; i++)
  {
    renderers.push_back(vtkSmartPointer<vtkRenderer>::New());
    static_cast<vtkRenderer*>(renderers.back().GetPointer())
        ->SetBackground(colors->GetColor3d("MidnightBlue").GetData());
    sortedNames.push_back("");
  }

  auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
  renderWindow->SetSize(rendererSize * gridColumnDimensions,
                        rendererSize * gridRowDimensions);

  for (auto row = 0; row < gridRowDimensions; row++)
  {
    for (auto col = 0; col < gridColumnDimensions; col++)
    {
      // (xmin, ymin, xmax, ymax)
      double viewport[4] = {
          static_cast<double>(col) * rendererSize /
              (gridColumnDimensions * rendererSize),
          static_cast<double>(gridRowDimensions - (row + 1)) * rendererSize /
              (gridRowDimensions * rendererSize),
          static_cast<double>(col + 1) * rendererSize /
              (gridColumnDimensions * rendererSize),
          static_cast<double>(gridRowDimensions - row) * rendererSize /
              (gridRowDimensions * rendererSize)};
      if (!cmdArgs["s"].first)
      {
        auto index = row * gridColumnDimensions + col;
        renderWindow->AddRenderer(renderers[index]);
        renderers[index]->SetViewport(viewport);
        if (index > objCount - 1)
        {
          continue;
        }
        renderers[index]->AddActor(actors[index]);
        // Normals can only be computed for polygons and triangle strips.
        // The Spline is a line.
        if (cmdArgs["n"].first && sortedNames[index] != "Spline")
        {
          renderers[index]->AddActor(glyphActor[index]);
        }
        renderers[index]->AddActor(textactors[index]);
        renderers[index]->SetBackground(
            colors->GetColor3d("MidnightBlue").GetData());
        renderers[index]->ResetCamera();
        renderers[index]->GetActiveCamera()->Azimuth(30);
        renderers[index]->GetActiveCamera()->Elevation(-30);
        renderers[index]->GetActiveCamera()->Zoom(0.9);
        renderers[index]->ResetCameraClippingRange();
      }

      else
      {
        auto index = singleSurface.second;
        if (index != -1)
        {
          renderWindow->AddRenderer(renderers[index]);
          renderers[index]->SetViewport(viewport);
          renderers[index]->AddActor(actors[index]);
          // Normals can only be computed for polygons and triangle strips.
          // The Spline is a line.
          if (cmdArgs["n"].first && singleSurface.first != "Spline")
          {
            renderers[index]->AddActor(glyphActor[index]);
          }
          renderers[index]->AddActor(textactors[index]);
          renderers[index]->SetBackground(
              colors->GetColor3d("MidnightBlue").GetData());
          renderers[index]->ResetCamera();
          renderers[index]->GetActiveCamera()->Azimuth(30);
          renderers[index]->GetActiveCamera()->Elevation(-30);
          renderers[index]->GetActiveCamera()->Zoom(0.9);
          renderers[index]->ResetCameraClippingRange();
        }
      }
    }
  }

  auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
  interactor->SetRenderWindow(renderWindow);

  renderWindow->Render();
  if (cmdArgs["s"].first)
  {
    renderWindow->SetWindowName(singleSurface.first.c_str());
  }
  else
  {
    renderWindow->SetWindowName("ParametricObjectsDemo");
  }
  renderWindow->Render();
  if (cmdArgs["w"].first)
  {
    // -------------------------------
    // Save the image
    // -------------------------------
    if (cmdArgs["s"].first)
    {
      WriteImage(singleSurface.first, renderWindow, false);
    }
    else
    {
      WriteImage("ParametricObjectsDemo", renderWindow, false);
    }
  }

  interactor->Start();

  return EXIT_SUCCESS;
}

namespace {

std::string ShowUsage(std::string fn)
{
  // Remove the folder (if present) then remove the extension in this order
  // since the folder name may contain perionds.
  auto last_slash_idx = fn.find_last_of("\\/");
  if (std::string::npos != last_slash_idx)
  {
    fn.erase(0, last_slash_idx + 1);
  }
  auto period_idx = fn.rfind('.');
  if (std::string::npos != period_idx)
  {
    fn.erase(period_idx);
  }
  std::ostringstream os;
  os << "\nusage: " << fn << "[-h][-s SURFACE_NAME][-w][-b][-n]\n\n"
     << "Display the parametric surfaces.\n\n"
     << "optional arguments:\n"
     << "  -h                    show this help message and exit\n"
     << "  -s SURFACE_NAME       The name of the surface.\n"
     << "  -w                    Write out the the image.\n"
     << "  -b                    Color the back-face.\n"
     << "  -n                    Display normals.\n"
     << std::endl;
  return os.str();
}

std::map<int, std::map<std::string, vtkSmartPointer<vtkParametricFunction>>>
GetParametricFunctions()
{
  std::map<int, std::map<std::string, vtkSmartPointer<vtkParametricFunction>>>
      pfn;
  pfn[0]["Boy"] = vtkSmartPointer<vtkParametricBoy>::New();
  pfn[0]["ConicSpiral"] = vtkSmartPointer<vtkParametricConicSpiral>::New();
  pfn[0]["CrossCap"] = vtkSmartPointer<vtkParametricCrossCap>::New();
  pfn[0]["Dini"] = vtkSmartPointer<vtkParametricDini>::New();
  pfn[0]["Ellipsoid"] = vtkSmartPointer<vtkParametricEllipsoid>::New();
  pfn[0]["Enneper"] = vtkSmartPointer<vtkParametricEnneper>::New();
  pfn[0]["Figure8Klein"] = vtkSmartPointer<vtkParametricFigure8Klein>::New();
  pfn[0]["Klein"] = vtkSmartPointer<vtkParametricKlein>::New();
  pfn[0]["Mobius"] = vtkSmartPointer<vtkParametricMobius>::New();
  pfn[0]["RandomHills"] = vtkSmartPointer<vtkParametricRandomHills>::New();
  pfn[0]["Roman"] = vtkSmartPointer<vtkParametricRoman>::New();
  pfn[0]["SuperEllipsoid"] =
      vtkSmartPointer<vtkParametricSuperEllipsoid>::New();
  pfn[0]["SuperToroid"] = vtkSmartPointer<vtkParametricSuperToroid>::New();
  pfn[0]["Torus"] = vtkSmartPointer<vtkParametricTorus>::New();
  pfn[0]["Spline"] = vtkSmartPointer<vtkParametricSpline>::New();
  // Extra parametric surfaces.
  pfn[1]["BohemianDome"] = vtkSmartPointer<vtkParametricBohemianDome>::New();
  pfn[1]["Bour"] = vtkSmartPointer<vtkParametricBour>::New();
  pfn[1]["CatalanMinimal"] =
      vtkSmartPointer<vtkParametricCatalanMinimal>::New();
  pfn[1]["Henneberg"] = vtkSmartPointer<vtkParametricHenneberg>::New();
  pfn[1]["Kuen"] = vtkSmartPointer<vtkParametricKuen>::New();
  pfn[1]["PluckerConoid"] = vtkSmartPointer<vtkParametricPluckerConoid>::New();
  pfn[1]["Pseudosphere"] = vtkSmartPointer<vtkParametricPseudosphere>::New();

  // Now set some parameters.
  static_cast<vtkParametricEllipsoid*>(pfn[0]["Ellipsoid"].GetPointer())
      ->SetXRadius(0.5);
  static_cast<vtkParametricEllipsoid*>(pfn[0]["Ellipsoid"].GetPointer())
      ->SetYRadius(2.0);
  static_cast<vtkParametricMobius*>(pfn[0]["Mobius"].GetPointer())
      ->SetRadius(2.0);
  static_cast<vtkParametricMobius*>(pfn[0]["Mobius"].GetPointer())
      ->SetMinimumV(-0.5);
  static_cast<vtkParametricMobius*>(pfn[0]["Mobius"].GetPointer())
      ->SetMaximumV(0.5);
  static_cast<vtkParametricRandomHills*>(pfn[0]["RandomHills"].GetPointer())
      ->AllowRandomGenerationOn();
  static_cast<vtkParametricRandomHills*>(pfn[0]["RandomHills"].GetPointer())
      ->SetRandomSeed(1);
  static_cast<vtkParametricRandomHills*>(pfn[0]["RandomHills"].GetPointer())
      ->SetNumberOfHills(30);
  static_cast<vtkParametricSuperEllipsoid*>(
      pfn[0]["SuperEllipsoid"].GetPointer())
      ->SetN1(0.5);
  static_cast<vtkParametricSuperEllipsoid*>(
      pfn[0]["SuperEllipsoid"].GetPointer())
      ->SetN2(0.4);
  static_cast<vtkParametricSuperToroid*>(pfn[0]["SuperToroid"].GetPointer())
      ->SetN1(0.5);
  static_cast<vtkParametricSuperToroid*>(pfn[0]["SuperToroid"].GetPointer())
      ->SetN2(3.0);
  // The spline needs points
  auto inputPoints = vtkSmartPointer<vtkPoints>::New();
  auto rng = vtkSmartPointer<vtkMinimalStandardRandomSequence>::New();
  rng->SetSeed(8775070);
  for (auto p = 0; p < 10; p++)
  {
    std::array<double, 3> xyz;
    for (auto& idx : xyz)
    {
      idx = rng->GetRangeValue(-1.0, 1.0);
      rng->Next();
    }
    inputPoints->InsertNextPoint(xyz.data());
  }
  static_cast<vtkParametricSpline*>(pfn[0]["Spline"].GetPointer())
      ->SetPoints(inputPoints);
  // Extra parametric surfaces.
  static_cast<vtkParametricBohemianDome*>(pfn[1]["BohemianDome"].GetPointer())
      ->SetA(5.0);
  static_cast<vtkParametricBohemianDome*>(pfn[1]["BohemianDome"].GetPointer())
      ->SetB(1.0);
  static_cast<vtkParametricBohemianDome*>(pfn[1]["BohemianDome"].GetPointer())
      ->SetC(2.0);
  static_cast<vtkParametricKuen*>(pfn[1]["Kuen"].GetPointer())
      ->SetDeltaV0(0.001);
  return pfn;
}

std::vector<double> GetCentre(const std::vector<double>& bounds)
{
  std::vector<double> centre;
  if (bounds.size() != 6)
  {
    return centre;
  }
  for (unsigned int i = 1; i < bounds.size(); i += 2)
  {
    centre.push_back(bounds[i] - (bounds[i] - bounds[i - 1]) / 2.0);
  }
  return centre;
}

double GetMaximumLength(const std::vector<double>& bounds)
{
  auto maxLen = -1.0;
  if (bounds.size() != 6)
  {
    return maxLen;
  }
  for (auto i = 0; i < int(bounds.size()); i += 2)
  {
    maxLen = std::max(maxLen, std::abs(bounds[i + 1] - bounds[i]));
  }
  return maxLen;
}

void DisplayBoundingBoxAndCenter(std::string const& name, int const& index,
                                 std::vector<double> const& bounds)
{
  if (bounds.size() != 6)
  {
    return;
  }
  auto maxLength = GetMaximumLength(bounds);
  auto centre = GetCentre(bounds);
  std::cout << std::left << std::setw(21) << name << std::right << ": "
            << std::setw(2) << index << "\n"
            << std::left << std::setw(21) << "  Bounds (min, max)"
            << ": x:(" << std::right << std::fixed << std::setw(6)
            << std::setprecision(2) << bounds[0] << ", " << std::setw(6)
            << bounds[1] << ") y:(" << std::setw(6) << bounds[2] << ", "
            << std::setw(6) << bounds[3] << ") z:(" << std::setw(6) << bounds[4]
            << ", " << std::setw(6) << bounds[5] << ")\n"
            << std::left << std::setw(21) << "  Maximum side length"
            << ": " << std::right << std::setw(6) << maxLength << "\n"
            << std::left << std::setw(21) << "  Centre (x, y, z)"
            << ": (" << std::right << std::setw(6) << centre[0] << ", "
            << std::setw(6) << centre[1] << ", " << std::setw(6) << centre[2]
            << ")\n"
            << std::endl;
}

void WriteImage(std::string const& fileName, vtkRenderWindow* renWin, bool rgba)
{
  if (!fileName.empty())
  {
    std::string fn = fileName;
    std::string path;
    std::string ext;
    auto found = fn.find_last_of(".");
    if (found == std::string::npos)
    {
      path = fn;
      ext = ".png";
      fn += ext;
    }
    else
    {
      path = fileName.substr(0, found);
      ext = fileName.substr(found, fileName.size());
    }
    std::locale loc;
    std::transform(ext.begin(), ext.end(), ext.begin(),
                   [=](char const& c) { return std::tolower(c, loc); });
    auto writer = vtkSmartPointer<vtkImageWriter>::New();
    if (ext == ".bmp")
    {
      writer = vtkSmartPointer<vtkBMPWriter>::New();
    }
    else if (ext == ".jpg")
    {
      writer = vtkSmartPointer<vtkJPEGWriter>::New();
    }
    else if (ext == ".pnm")
    {
      writer = vtkSmartPointer<vtkPNMWriter>::New();
    }
    else if (ext == ".ps")
    {
      if (rgba)
      {
        rgba = false;
      }
      writer = vtkSmartPointer<vtkPostScriptWriter>::New();
    }
    else if (ext == ".tiff")
    {
      writer = vtkSmartPointer<vtkTIFFWriter>::New();
    }
    else
    {
      writer = vtkSmartPointer<vtkPNGWriter>::New();
    }
    auto window_to_image_filter =
        vtkSmartPointer<vtkWindowToImageFilter>::New();
    window_to_image_filter->SetInput(renWin);
    window_to_image_filter->SetScale(1); // image quality
    if (rgba)
    {
      window_to_image_filter->SetInputBufferTypeToRGBA();
    }
    else
    {
      window_to_image_filter->SetInputBufferTypeToRGB();
    }
    // Read from the front buffer.
    window_to_image_filter->ReadFrontBufferOff();
    window_to_image_filter->Update();

    writer->SetFileName(fn.c_str());
    writer->SetInputConnection(window_to_image_filter->GetOutputPort());
    writer->Write();
  }
  else
  {
    std::cerr << "No filename provided." << std::endl;
  }

  return;
}

CommandLineParser::CommandLineParser(
    std::vector<std::string>& cmdLineVec,
    std::map<std::string, std::string>& optArgs,
    std::map<std::string, std::string>& optArgsParam, int posNum,
    std::string const& posName)
  : cmdLineVec(cmdLineVec),
    optArgs(optArgs),
    optArgsParam(optArgsParam),
    posNum(posNum),
    posName(posName)
{
  // Make a set of all the values.
  std::set<std::string> pVals;
  this->GetSetOfValues(this->optArgs, pVals);
  this->GetSetOfValues(this->optArgsParam, pVals);

  // Set the default arguments
  for (auto p : pVals)
  {
    this->cmdArgs[p].first = false;
  }

  // Get the aliases
  this->FindAliases(this->optArgsParam, this->aliases);
  this->FindAliases(this->optArgs, this->aliases);

  cl = this->SeparateKV();
}

CommandLineParser::~CommandLineParser() = default;

std::vector<std::string> CommandLineParser::SeparateKV()
{
  // For keys with parameters, short commands like -xy need to be split into
  // -x y.
  std::vector<std::string> cl;
  if (!this->optArgsParam.empty())
  {
    for (auto v : this->cmdLineVec)
    {
      auto a = v.substr(0, 2);
      if (this->optArgsParam.count(a) > 0 && v.size() > 2)
      {
        cl.push_back(a);
        cl.push_back(v.substr(2));
      }
      else
      {
        cl.push_back(v);
      }
    }
  }
  return cl;
}

bool CommandLineParser::HasUnknownKeys()
{
  auto hasKV = !this->optArgsParam.empty();
  auto hasNKV = !this->optArgs.empty();
  std::vector<std::string> unknownKeys;
  for (auto v : this->cl)
  {
    if (v[0] == '-' || v.substr(0, 2) == "--")
    {
      if ((hasKV && this->optArgsParam.count(v) > 0) ||
          (hasNKV && this->optArgs.count(v) > 0))
      {
        continue;
      }
      else
      {
        unknownKeys.push_back(v);
      }
    }
  }
  if (unknownKeys.size() > 0)
  {
    std::ostringstream os;
    os << "Unknown parameters found: ";
    std::copy(std::begin(unknownKeys), std::prev(std::end(unknownKeys)),
              std::ostream_iterator<std::string>(os, ", "));
    os << unknownKeys.back();
    this->parseError = os.str();
    return true;
  }
  return false;
}

bool CommandLineParser::HasDuplicateKeys()
{
  // Look for duplicate non-positional parameters.
  for (auto k : this->optArgsParam)
  {
    auto c = std::count(this->cl.begin(), this->cl.end(), k.first);
    this->aliases[k.second].second += int(c);
  }
  for (auto k : this->optArgs)
  {
    auto c = std::count(this->cl.begin(), this->cl.end(), k.first);
    this->aliases[k.second].second += int(c);
  }
  std::map<std::string, std::vector<std::string>> duplicates;
  for (auto v : this->aliases)
  {
    if (v.second.second > 1)
    {
      duplicates[v.first] = v.second.first;
    }
  }
  if (duplicates.size() > 0)
  {
    std::ostringstream os;
    os << "Duplicated parameters found: ";
    for (auto d : duplicates)
    {
      os << "(";
      std::copy(std::begin(d.second), std::prev(std::end(d.second)),
                std::ostream_iterator<std::string>(os, ", "));
      os << d.second.back();
      os << ") ";
    }
    // Get rid of trailing spaces
    auto last = os.str().find_last_not_of(' ');
    this->parseError = os.str().substr(0, last + 1);
    return true;
  }
  return false;
}

bool CommandLineParser::Parse()
{
  this->parseError.clear();

  if (!this->cl.empty())
  {
    if (HasUnknownKeys() || HasDuplicateKeys())
    {
      return false;
    }

    std::vector<std::string> positionalArguments;
    for (auto it = this->cl.begin(); it != this->cl.end(); ++it)
    {
      if (!this->optArgs.empty())
      {
        if (this->optArgs.count(*it) > 0)
        {
          this->cmdArgs[this->optArgs[*it]].first = true;
          continue;
        }
      }
      if (!this->optArgsParam.empty())
      {
        if (this->optArgsParam.count(*it) > 0)
        {
          if (std::next(it) != this->cl.end())
          {
            auto key = this->optArgsParam[*it];
            while (std::next(it) != this->cl.end())
            {
              if ((*std::next(it))[0] == '-')
              {
                break;
              }
              ++it;

              std::string fn = *it;
              if (fn[0] != '-')
              {
                if (!this->cmdArgs[key].first)
                {
                  this->cmdArgs[key].first = true;
                }
                this->cmdArgs[key].second.push_back(fn);
              }
              else
              {
                if (this->cmdArgs[key].second.empty())
                {
                  std::ostringstream os;
                  os << key << " must be followed by a parameter.";
                  this->parseError = os.str();
                  return false;
                }
                else
                {
                  break;
                }
              }
            }
          }
          else
          {
            std::ostringstream os;
            os << *it
               << " must be followed by a parameter, reached the end of the "
                  "commands.";
            this->parseError = os.str();
            return false;
          }
          continue;
        }
      }
      positionalArguments.push_back(*it);
    }
    if (this->posNum != int(positionalArguments.size()))
    {
      std::ostringstream os;
      os << "Expected " << this->posNum << " positional arguments, got "
         << positionalArguments.size() << " instead.";
      this->parseError = os.str();
      return false;
    }
    if (!positionalArguments.empty())
    {
      this->cmdArgs[this->posName].first = true;
      this->cmdArgs[this->posName].second = positionalArguments;
    }
    else
    {
      this->cmdArgs[this->posName].first = false;
    }
  }
  return true;
}

std::string CommandLineParser::DisplayCommandArguments()
{
  std::vector<std::string> setParameters;
  std::vector<std::string> unSetParameters;
  for (auto k : this->cmdArgs)
  {
    if (this->cmdArgs[k.first].first)
    {
      setParameters.push_back(k.first);
    }
    else
    {
      unSetParameters.push_back(k.first);
    }
  }
  // Sort them
  std::sort(setParameters.begin(), setParameters.end());
  std::sort(unSetParameters.begin(), unSetParameters.end());

  std::ostringstream os;
  if (setParameters.size() > 0)
  {
    os << "Set parameters:" << std::endl;
    for (auto k : setParameters)
    {
      if (this->cmdArgs[k].first)
      {
        os << "  " << k;
        if (!this->cmdArgs[k].second.empty())
        {
          os << ": ";
          std::copy(std::begin(cmdArgs[k].second),
                    std::prev(std::end(cmdArgs[k].second)),
                    std::ostream_iterator<std::string>(os, ", "));
          os << this->cmdArgs[k].second.back();
        }
        os << std::endl;
      }
    }
  }

  if (unSetParameters.size() > 0)
  {
    os << "Unset parameters:" << std::endl;
    for (auto k : unSetParameters)
    {
      if (!this->cmdArgs[k].first)
      {
        os << "  " << k;
        if (!this->cmdArgs[k].second.empty())
        {
          os << ": ";
          std::copy(std::begin(this->cmdArgs[k].second),
                    std::prev(std::end(this->cmdArgs[k].second)),
                    std::ostream_iterator<std::string>(os, ", "));
          os << this->cmdArgs[k].second.back();
        }
        os << std::endl;
      }
    }
  }
  return os.str();
}

template <typename K, typename V>
void CommandLineParser::GetSetOfValues(std::map<K, V> const& m, std::set<V>& s)
{
  std::for_each(m.begin(), m.end(), [&](const std::pair<const K, V>& element) {
    s.insert(element.second);
  });
}

template <typename K, typename V>
void CommandLineParser::FindAliases(
    std::map<K, V> m, std::map<V, std::pair<std::vector<K>, int>>& aliases)
{
  std::set<V> values;
  this->GetSetOfValues(m, values);
  for (auto v : values)
  {
    std::vector<K> a;
    for (auto it : m)
    {
      if (it.second == v)
      {
        a.push_back(it.first);
      }
    }
    if (a.size() > 0)
    {
      aliases[v] = std::pair<std::vector<K>, int>(a, 0);
    }
  }
}

} // namespace

CMakeLists.txt

cmake_minimum_required(VERSION 3.3 FATAL_ERROR)

project(ParametricObjectsDemo)

find_package(VTK COMPONENTS 
  vtkCommonColor
  vtkCommonComputationalGeometry
  vtkCommonCore
  vtkCommonDataModel
  vtkFiltersCore
  vtkFiltersSources
  vtkIOImage
  vtkInteractionStyle
  vtkRenderingContextOpenGL2
  vtkRenderingCore
  vtkRenderingFreeType
  vtkRenderingGL2PSOpenGL2
  vtkRenderingOpenGL2 QUIET)
if (NOT VTK_FOUND)
  message("Skipping ParametricObjectsDemo: ${VTK_NOT_FOUND_MESSAGE}")
  return ()
endif()
message (STATUS "VTK_VERSION: ${VTK_VERSION}")
if (VTK_VERSION VERSION_LESS "8.90.0")
  # old system
  include(${VTK_USE_FILE})
  add_executable(ParametricObjectsDemo MACOSX_BUNDLE ParametricObjectsDemo.cxx )
  target_link_libraries(ParametricObjectsDemo PRIVATE ${VTK_LIBRARIES})
else ()
  # include all components
  add_executable(ParametricObjectsDemo MACOSX_BUNDLE ParametricObjectsDemo.cxx )
  target_link_libraries(ParametricObjectsDemo PRIVATE ${VTK_LIBRARIES})
  # vtk_module_autoinit is needed
  vtk_module_autoinit(
    TARGETS ParametricObjectsDemo
    MODULES ${VTK_LIBRARIES}
    )
endif () 

Download and Build ParametricObjectsDemo

Click here to download ParametricObjectsDemo and its CMakeLists.txt file. Once the tarball ParametricObjectsDemo.tar has been downloaded and extracted,

cd ParametricObjectsDemo/build 

If VTK is installed:

cmake ..

If VTK is not installed but compiled on your system, you will need to specify the path to your VTK build:

cmake -DVTK_DIR:PATH=/home/me/vtk_build ..

Build the project:

make

and run it:

./ParametricObjectsDemo

WINDOWS USERS

Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.