We can use vtkParametricTorus to construct a curved 3D arrow. However, during this process, the opening needs to be closed, and the boundary should be preprocessed: merge nearby points, remove duplicate points, and triangulate.
RotationArrow.h
#pragma once
#include <vtkSmartPointer.h>
#include <vtkPolyData.h>
#include <vtkMath.h>
#include <vtkRenderer.h>
#include "Point.hpp"
class RotationArrow
{
public:
RotationArrow();
vtkSPtr<vtkPolyData> Generate(double radius = 1.0);
private:
vtkSPtr<vtkPolyData> CreateArrowBody(double ringRadius = 1.0, double crossSectionRadius = 0.2, double minU = 0, double maxU = 2 * vtkMath::Pi() );
vtkSPtr<vtkPolyData> CreateCircularArc(double radius, double startAngle, double endAngle, int resolution);
vtkSPtr<vtkPolyData> CreateArrowHead(const Point& position, const Point& direction, double length);
vtkSPtr<vtkPolyData> GetDataForShow( vtkSPtr<vtkTransform> trans, vtkSPtr<vtkPolyData> data );
};
RotationArrow.cpp
#include "RotationArrow.h"
#include "BasicMethod.h"
#include "ConnectedEdgeFilter.h"
#include <vtkCleanPolyData.h>
#include <vtkTriangleFilter.h>
#include <vtkFillHolesFilter.h>
#include <vtkTubeFilter.h>
#include <vtkAppendPolyData.h>
#include <vtkConeSource.h>
#include <vtkParametricFunctionSource.h>
#include <vtkParametricTorus.h>
vtkSPtr<vtkPolyData> RotationArrow::GetDataForShow( vtkSPtr<vtkTransform> trans, vtkSPtr<vtkPolyData> data )
{
vtkSPtrNew( transFilter, vtkTransformFilter );
transFilter->SetTransform( trans );
transFilter->SetInputData( data );
transFilter->Update();
vtkSPtr<vtkPolyData> result = transFilter->GetPolyDataOutput();
return result;
}
RotationArrow::RotationArrow()
{
}
/*
x = r * cos(θ)
y = r * sin(θ)
for any angle, the derivative of the x and y coordinates with respect to θ are:
dx/dθ = -r * sin(θ)
dy/dθ = r * cos(θ)
*/
vtkSPtr<vtkPolyData> RotationArrow::Generate(double radius)
{
double angleValue = 270;
double crossSectionRadius = 0.05*radius;
auto body = CreateArrowBody(radius, crossSectionRadius, 0, vtkMath::RadiansFromDegrees(angleValue));
vtkSPtrNew( cleanFilter, vtkCleanPolyData );
cleanFilter->SetInputData( body );
cleanFilter->SetTolerance( 1e-6 );
cleanFilter->PointMergingOn();
cleanFilter->Update();
body = cleanFilter->GetOutput();
vtkSPtrNew( triangleFilter, vtkTriangleFilter );
triangleFilter->SetInputData( body );
triangleFilter->SetPassLines( false );
triangleFilter->SetPassVerts( false );
triangleFilter->Update();
body = triangleFilter->GetOutput();
body->BuildCells();
body->BuildLinks();
vtkSPtrNew( edges, vtkCellArray );
BasicMethod::FindIndependentEdges(edges, body);
ConnectedEdgeFilter *connectFilter = new ConnectedEdgeFilter();
connectFilter->Initialise( edges );
connectFilter->HandleEdges();
auto listCount = connectFilter->GetListsCount();
for( int i = 0; i < listCount; i++ )
{
auto list = connectFilter->GetList(i);
if( list->GetNumberOfIds() < 2 )
continue;
BasicMethod::CenterPointFillHole( body, list );
}
double endAngle = vtkMath::RadiansFromDegrees(angleValue);
Point arrowPos(radius * cos(endAngle), radius * sin(endAngle), 0);
Point tangent(-sin(endAngle), cos(endAngle), 0);
auto arrowHead = CreateArrowHead(arrowPos, tangent, crossSectionRadius * 8);
vtkSPtrNew(append, vtkAppendPolyData);
append->AddInputData(body);
append->AddInputData(arrowHead);
append->Update();
return append->GetOutput();
}
vtkSPtr<vtkPolyData> RotationArrow::CreateArrowBody(double ringRadius, double crossSectionRadius, double minU, double maxU)
{
vtkSPtrNew( source, vtkParametricFunctionSource );
vtkSPtrNew( func, vtkParametricTorus );
func->SetMinimumU( minU );
func->SetMaximumU( maxU );
func->SetJoinU(false);
func->SetRingRadius( ringRadius );
func->SetCrossSectionRadius( crossSectionRadius );
source->SetParametricFunction( func );
source->Update();
vtkSPtrNew(rotateTrans, vtkTransform);
rotateTrans->RotateWXYZ(-180, 0, 0, 1);
rotateTrans->Update();
auto result = GetDataForShow(rotateTrans, source->GetOutput());
vtkSPtrNew(triangleFilter, vtkTriangleFilter);
triangleFilter->SetInputData(result);
triangleFilter->PassLinesOff();
triangleFilter->PassVertsOff();
triangleFilter->Update();
return triangleFilter->GetOutput();
}
vtkSPtr<vtkPolyData> RotationArrow::CreateCircularArc(double radius, double startAngle, double endAngle, int resolution)
{
vtkSPtrNew(points, vtkPoints);
vtkSPtrNew(cells, vtkCellArray);
double angleStep = (endAngle - startAngle) / (resolution - 1);
vtkIdType prevId = -1;
for(int i = 0; i < resolution; i++)
{
double angle = vtkMath::RadiansFromDegrees(startAngle + i * angleStep);
double x = radius * cos(angle);
double y = radius * sin(angle);
vtkIdType id = points->InsertNextPoint(x, y, 0);
if(prevId >= 0)
{
vtkIdType line[2] = {prevId, id};
cells->InsertNextCell(2, line);
}
prevId = id;
}
vtkSPtrNew(polyData, vtkPolyData);
polyData->SetPoints(points);
polyData->SetLines(cells);
return polyData;
}
vtkSPtr<vtkPolyData> RotationArrow::CreateArrowHead(const Point& position, const Point& direction, double length)
{
vtkSPtrNew(cone, vtkConeSource);
cone->SetHeight(length);
cone->SetRadius(length * 0.6);
cone->SetResolution(16);
cone->Update();
auto trans = BasicMethod::RotateVecToSpecialDir(Point(1, 0, 0), direction);
vtkSPtrNew( finalTrans, vtkTransform );
finalTrans->Translate(position[0], position[1], position[2]);
finalTrans->Update();
finalTrans->Concatenate(trans);
finalTrans->Update();
return GetDataForShow(finalTrans, cone->GetOutput());
}
Some interfaces of BasicMethod can be found on the websize.
