ParametricObjectsDemo
Repository source: 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)
- Color the back-face (-b)
- Add normals (-n)
- Display the geometric bounds of the object (-l)
You can save a screenshot by pressing "k".
With respect to your VTK build you may need to specify one or more of:
-DVTK_MODULE_ENABLE_VTK_cli11=WANT
-DVTK_MODULE_ENABLE_VTK_fmt=WANT
If -DVTK_BUILD_TESTING=ON
is specified when building VTK then VTK:cli11
and VTK::fmt
will be automatically enabled.
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 provides a description of 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), (PythonicAPI), (CSharp)
Question
If you have a question about this example, please use the VTK Discourse Forum
Code¶
ParametricObjectsDemo.cxx
#include <vtkActor.h>
#include <vtkActor2D.h>
#include <vtkCallbackCommand.h>
#include <vtkCamera.h>
#include <vtkMath.h>
#include <vtkMinimalStandardRandomSequence.h>
#include <vtkNamedColors.h>
#include <vtkNew.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 <vtk_cli11.h>
#include <vtk_fmt.h>
// clang-format off
#include VTK_FMT(fmt/format.h)
// clang-format on
#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 <vtkGlyph3D.h>
#include <vtkMaskPoints.h>
// For writing out the image.
#include <vtkPNGWriter.h>
#include <vtkWindowToImageFilter.h>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <locale>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
namespace {
/**
* Create a map of the parametric functions and set some parameters.
*
* @return The map of functions.
*/
std::map<std::string, vtkSmartPointer<vtkParametricFunction>>
GetParametricFunctions();
/**
* 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 bounds: The bounding box of the object.
*
*/
void DisplayBoundingBoxAndCenter(std::string const& name,
std::vector<double> const& bounds);
class PrintCallback : public vtkCallbackCommand
{
public:
PrintCallback() : fn_{""}, imageQuality_(1), rgba_(true)
{
}
static PrintCallback* New()
{
return new PrintCallback;
}
/*
* Create a vtkCallbackCommand and reimplement it.
*
*/
void Execute(vtkObject* caller, unsigned long /*evId*/, void*) override
{
// Note the use of reinterpret_cast to cast the caller to the expected type.
auto rwi = reinterpret_cast<vtkRenderWindowInteractor*>(caller);
// Get the keypress
std::string key = rwi->GetKeySym();
if (key == "k" && !fn_.empty())
{
auto w2If = vtkSmartPointer<vtkWindowToImageFilter>::New();
w2If->SetInput(rwi->GetRenderWindow());
w2If->SetScale(this->imageQuality_, this->imageQuality_);
if (rgba_)
{
w2If->SetInputBufferTypeToRGBA();
}
else
{
w2If->SetInputBufferTypeToRGB();
}
// Read from the front buffer.
w2If->ReadFrontBufferOn();
w2If->Update();
auto writer = vtkSmartPointer<vtkPNGWriter>::New();
writer->SetFileName(fn_.c_str());
writer->SetInputConnection(w2If->GetOutputPort());
writer->Write();
std::cout << "Screenshot saved to: " << writer->GetFileName()
<< std::endl;
}
}
/**
* Set the parameters for writing the
* the render window view to an image file.
*
* @param fileName The png image file name.
* @param imageQuality The image quality.
* @param rgba The buffer type, (if true, there is no background in the
* screenshot).
*
*/
void SetParameters(const std::string& fileName, int imageQuality = 1,
bool rgba = true)
{
if (!fileName.empty())
{
this->fn_ = fileName;
std::string ext{".png"};
auto found = this->fn_.find_last_of(".");
if (found == std::string::npos)
{
this->fn_ += ext;
}
else
{
this->fn_ = fileName.substr(0, fileName.find_last_of(".")) += ext;
}
}
this->imageQuality_ = imageQuality;
this->rgba_ = rgba;
}
private:
PrintCallback(const PrintCallback&) = delete;
void operator=(const PrintCallback&) = delete;
std::string fn_;
int imageQuality_;
bool rgba_;
};
} // namespace
int main(int argc, char* argv[])
{
CLI::App app{"Display the parametric surfaces."};
// Define options
std::string surfaceName;
app.add_option("-s, --surface_name", surfaceName, "The name of the surface.");
auto backFace{false};
app.add_flag("-b, --back_face", backFace, "Color the back face.");
auto normals{false};
app.add_flag("-n, --normals", normals, "Display normals.");
auto limits{false};
app.add_flag("-l, --limits", limits,
"Display the geometric bounds of the object.");
CLI11_PARSE(app, argc, argv);
// Get the parametric functions.
auto pfn = GetParametricFunctions();
// Check for a single surface.
std::pair<std::string, bool> singleSurface{"", false};
if (!surfaceName.empty())
{
std::string sn = surfaceName;
std::transform(sn.begin(), sn.end(), sn.begin(),
[](unsigned char c) { return std::tolower(c); });
// Is the surface name in the map?
for (auto const& t : pfn)
{
std::string k = t.first;
std::transform(k.begin(), k.end(), k.begin(),
[](unsigned char c) { return std::tolower(c); });
if (sn == k)
{
singleSurface.first = t.first;
singleSurface.second = true;
}
}
}
if (!surfaceName.empty() && !singleSurface.second)
{
std::cout << "Nonexistent surface: " << surfaceName << std::endl;
return EXIT_FAILURE;
}
auto rendererSize = 200;
auto gridColumnDimensions = 5;
auto gridRowDimensions = 5;
if (singleSurface.second)
{
rendererSize = 1000;
gridColumnDimensions = 1;
gridRowDimensions = 1;
}
vtkNew<vtkNamedColors> colors;
// Create one text property for all.
vtkNew<vtkTextProperty> textProperty;
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 (backFace)
{
backProperty->SetColor(colors->GetColor3d("Peru").GetData());
}
// Now decide on the surfaces to build.
std::map<std::string, vtkSmartPointer<vtkParametricFunction>> surfaces;
if (singleSurface.second)
{
surfaces[singleSurface.first] = pfn[singleSurface.first];
}
else
{
surfaces = pfn;
}
// The bounding boxes for each object.
std::map<std::string, std::vector<double>> boundingBoxes;
std::map<int, std::string> indexedNames;
// The index of each parametric object.
auto objIdx = -1;
for (auto const& obj : surfaces)
{
objIdx++;
indexedNames[objIdx] = obj.first;
pfnSrcs.push_back(vtkSmartPointer<vtkParametricFunctionSource>::New());
pfnSrcs[objIdx]->SetParametricFunction(obj.second);
pfnSrcs[objIdx]->SetUResolution(51);
pfnSrcs[objIdx]->SetVResolution(51);
pfnSrcs[objIdx]->SetWResolution(51);
pfnSrcs[objIdx]->Update();
mappers.push_back(vtkSmartPointer<vtkPolyDataMapper>::New());
mappers[objIdx]->SetInputConnection(pfnSrcs[objIdx]->GetOutputPort());
actors.push_back(vtkSmartPointer<vtkActor>::New());
actors[objIdx]->SetMapper(mappers[objIdx]);
actors[objIdx]->GetProperty()->SetColor(
colors->GetColor3d("NavajoWhite").GetData());
if (backFace)
{
actors[objIdx]->SetBackfaceProperty(backProperty);
}
textmappers.push_back(vtkSmartPointer<vtkTextMapper>::New());
textmappers[objIdx]->SetInput(obj.first.c_str());
textmappers[objIdx]->SetTextProperty(textProperty);
textactors.push_back(vtkSmartPointer<vtkActor2D>::New());
textactors[objIdx]->SetMapper(textmappers[objIdx]);
textactors[objIdx]->SetPosition(rendererSize / 2.0, 8);
renderers.push_back(vtkSmartPointer<vtkRenderer>::New());
renderers[objIdx]->SetBackground(
colors->GetColor3d("MidnightBlue").GetData());
double bounds[6];
pfnSrcs[objIdx]->GetOutput()->GetBounds(bounds);
std::vector<double> v(std::begin(bounds), std::end(bounds));
boundingBoxes[obj.first] = v;
if (normals)
{
// Glyphing
maskPts.push_back(vtkSmartPointer<vtkMaskPoints>::New());
maskPts[objIdx]->RandomModeOn();
maskPts[objIdx]->SetMaximumNumberOfPoints(150);
maskPts[objIdx]->SetInputConnection(pfnSrcs[objIdx]->GetOutputPort());
arrow.push_back(vtkSmartPointer<vtkArrowSource>::New());
arrow[objIdx]->SetTipResolution(16);
arrow[objIdx]->SetTipLength(0.3);
arrow[objIdx]->SetTipRadius(0.1);
auto glyphScale = GetMaximumLength(boundingBoxes[obj.first]);
glyph.push_back(vtkSmartPointer<vtkGlyph3D>::New());
glyph[objIdx]->SetSourceConnection(arrow[objIdx]->GetOutputPort());
glyph[objIdx]->SetInputConnection(maskPts[objIdx]->GetOutputPort());
glyph[objIdx]->SetVectorModeToUseNormal();
glyph[objIdx]->SetScaleFactor(glyphScale / 10.0);
glyph[objIdx]->OrientOn();
glyph[objIdx]->Update();
glyphMapper.push_back(vtkSmartPointer<vtkPolyDataMapper>::New());
glyphMapper[objIdx]->SetInputConnection(glyph[objIdx]->GetOutputPort());
glyphActor.push_back(vtkSmartPointer<vtkActor>::New());
glyphActor[objIdx]->SetMapper(glyphMapper[objIdx]);
glyphActor[objIdx]->GetProperty()->SetColor(
colors->GetColor3d("GreenYellow").GetData());
}
}
// Need a renderer even if there is no actor.
for (auto i = objIdx; i < gridColumnDimensions * gridRowDimensions; i++)
{
renderers.push_back(vtkSmartPointer<vtkRenderer>::New());
static_cast<vtkRenderer*>(renderers.back().GetPointer())
->SetBackground(colors->GetColor3d("MidnightBlue").GetData());
indexedNames[i] = "";
}
auto renWin = vtkSmartPointer<vtkRenderWindow>::New();
renWin->SetSize(rendererSize * gridColumnDimensions,
rendererSize * gridRowDimensions);
for (auto row = 0; row < gridRowDimensions; row++)
{
for (auto col = 0; col < gridColumnDimensions; col++)
{
auto index = row * 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)};
renWin->AddRenderer(renderers[index]);
renderers[index]->SetViewport(viewport);
if (index > objIdx)
{
continue;
}
renderers[index]->AddActor(actors[index]);
// Normals can only be computed for polygons and triangle strips.
// The Spline is a line.
if (normals && indexedNames[index] != "Spline")
{
renderers[index]->AddActor(glyphActor[index]);
}
renderers[index]->AddActor(textactors[index]);
renderers[index]->ResetCamera();
renderers[index]->GetActiveCamera()->Azimuth(30);
renderers[index]->GetActiveCamera()->Elevation(-30);
renderers[index]->GetActiveCamera()->Zoom(0.9);
renderers[index]->ResetCameraClippingRange();
}
}
if (limits)
{
for (auto const& obj : boundingBoxes)
{
DisplayBoundingBoxAndCenter(obj.first, obj.second);
}
}
std::string fn = "ParametricObjectsDemo";
if (!surfaceName.empty())
{
fn = singleSurface.first;
}
renWin->SetWindowName(fn.c_str());
vtkNew<vtkRenderWindowInteractor> iRen;
vtkNew<PrintCallback> printCallback;
printCallback->SetParameters(fn, 1, false);
iRen->SetRenderWindow(renWin);
iRen->AddObserver(vtkCommand::KeyPressEvent, printCallback);
iRen->Initialize();
iRen->Start();
return EXIT_SUCCESS;
}
namespace {
std::map<std::string, vtkSmartPointer<vtkParametricFunction>>
GetParametricFunctions()
{
std::map<std::string, vtkSmartPointer<vtkParametricFunction>> pfn;
pfn["Boy"] = vtkSmartPointer<vtkParametricBoy>::New();
pfn["ConicSpiral"] = vtkSmartPointer<vtkParametricConicSpiral>::New();
pfn["CrossCap"] = vtkSmartPointer<vtkParametricCrossCap>::New();
pfn["Dini"] = vtkSmartPointer<vtkParametricDini>::New();
pfn["Ellipsoid"] = vtkSmartPointer<vtkParametricEllipsoid>::New();
pfn["Enneper"] = vtkSmartPointer<vtkParametricEnneper>::New();
pfn["Figure8Klein"] = vtkSmartPointer<vtkParametricFigure8Klein>::New();
pfn["Klein"] = vtkSmartPointer<vtkParametricKlein>::New();
pfn["Mobius"] = vtkSmartPointer<vtkParametricMobius>::New();
pfn["RandomHills"] = vtkSmartPointer<vtkParametricRandomHills>::New();
pfn["Roman"] = vtkSmartPointer<vtkParametricRoman>::New();
pfn["SuperEllipsoid"] = vtkSmartPointer<vtkParametricSuperEllipsoid>::New();
pfn["SuperToroid"] = vtkSmartPointer<vtkParametricSuperToroid>::New();
pfn["Torus"] = vtkSmartPointer<vtkParametricTorus>::New();
pfn["Spline"] = vtkSmartPointer<vtkParametricSpline>::New();
// Extra parametric surfaces.
pfn["BohemianDome"] = vtkSmartPointer<vtkParametricBohemianDome>::New();
pfn["Bour"] = vtkSmartPointer<vtkParametricBour>::New();
pfn["CatalanMinimal"] = vtkSmartPointer<vtkParametricCatalanMinimal>::New();
pfn["Henneberg"] = vtkSmartPointer<vtkParametricHenneberg>::New();
pfn["Kuen"] = vtkSmartPointer<vtkParametricKuen>::New();
pfn["PluckerConoid"] = vtkSmartPointer<vtkParametricPluckerConoid>::New();
pfn["Pseudosphere"] = vtkSmartPointer<vtkParametricPseudosphere>::New();
// Now set some parameters.
static_cast<vtkParametricEllipsoid*>(pfn["Ellipsoid"].GetPointer())
->SetXRadius(0.5);
static_cast<vtkParametricEllipsoid*>(pfn["Ellipsoid"].GetPointer())
->SetYRadius(2.0);
static_cast<vtkParametricMobius*>(pfn["Mobius"].GetPointer())->SetRadius(2.0);
static_cast<vtkParametricMobius*>(pfn["Mobius"].GetPointer())
->SetMinimumV(-0.5);
static_cast<vtkParametricMobius*>(pfn["Mobius"].GetPointer())
->SetMaximumV(0.5);
static_cast<vtkParametricRandomHills*>(pfn["RandomHills"].GetPointer())
->AllowRandomGenerationOn();
static_cast<vtkParametricRandomHills*>(pfn["RandomHills"].GetPointer())
->SetRandomSeed(1);
static_cast<vtkParametricRandomHills*>(pfn["RandomHills"].GetPointer())
->SetNumberOfHills(30);
static_cast<vtkParametricSuperEllipsoid*>(pfn["SuperEllipsoid"].GetPointer())
->SetN1(0.5);
static_cast<vtkParametricSuperEllipsoid*>(pfn["SuperEllipsoid"].GetPointer())
->SetN2(0.4);
static_cast<vtkParametricSuperToroid*>(pfn["SuperToroid"].GetPointer())
->SetN1(0.5);
static_cast<vtkParametricSuperToroid*>(pfn["SuperToroid"].GetPointer())
->SetN2(3.0);
// The spline needs points
vtkNew<vtkPoints> inputPoints;
vtkNew<vtkMinimalStandardRandomSequence> rng;
rng->SetSeed(8775070);
for (auto p = 0; p < 10; p++)
{
std::array<double, 3> xyz{0, 0, 0};
for (auto& idx : xyz)
{
idx = rng->GetRangeValue(-1.0, 1.0);
rng->Next();
}
inputPoints->InsertNextPoint(xyz.data());
}
static_cast<vtkParametricSpline*>(pfn["Spline"].GetPointer())
->SetPoints(inputPoints);
// Extra parametric surfaces.
static_cast<vtkParametricBohemianDome*>(pfn["BohemianDome"].GetPointer())
->SetA(5.0);
static_cast<vtkParametricBohemianDome*>(pfn["BohemianDome"].GetPointer())
->SetB(1.0);
static_cast<vtkParametricBohemianDome*>(pfn["BohemianDome"].GetPointer())
->SetC(2.0);
static_cast<vtkParametricKuen*>(pfn["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 (size_t 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 (size_t i = 0; i < bounds.size(); i += 2)
{
maxLen = std::max(maxLen, std::abs(bounds[i + 1] - bounds[i]));
}
return maxLen;
}
void DisplayBoundingBoxAndCenter(std::string const& name,
std::vector<double> const& bounds)
{
if (bounds.size() != 6)
{
return;
}
auto maxLength = GetMaximumLength(bounds);
auto centre = GetCentre(bounds);
auto s = fmt::format("{:<21s}\n", name);
s += fmt::format("{:21s}: ", " Bounds (min, max)");
s += fmt::format("{:s}:({:6.2f}, {:6.2f}) ", "x", bounds[0], bounds[1]);
s += fmt::format("{:s}:({:6.2f}, {:6.2f}) ", "y", bounds[2], bounds[3]);
s += fmt::format("{:s}:({:6.2f}, {:6.2f})\n", "z", bounds[4], bounds[5]);
s += fmt::format("{:21s}: {:6.2f}\n", " Maximum side length", maxLength);
s += fmt::format("{:21s}: ({:6.2f}, {:6.2f}, {:6.2f})\n",
" Centre (x, y, z)", centre[0], centre[1], centre[2]);
std::cout << s << std::endl;
}
} // namespace
CMakeLists.txt¶
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
project(ParametricObjectsDemo)
find_package(VTK COMPONENTS
CommonColor
CommonComputationalGeometry
CommonCore
FiltersCore
FiltersSources
IOImage
InteractionStyle
RenderingContextOpenGL2
RenderingCore
RenderingFreeType
RenderingGL2PSOpenGL2
RenderingOpenGL2
cli11
fmt
)
if (NOT VTK_FOUND)
message(FATAL_ERROR "ParametricObjectsDemo: Unable to find the VTK build folder.")
endif()
# Prevent a "command line is too long" failure in Windows.
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
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}
)
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.