How to use the vtkFrenetSerretFrame to make a dental panorama, also known as CPR?

I have added my extension here, which is quite convenient for working with TNB. You have a callback for drawing the TNB frame.

#pragma once

#include "vtkOrientedGlyphContourRepresentation.h"
#include "vtkInteractionWidgetsModule.h" // For export macro

class vtkProperty;
class vtkActor;
class vtkGlyph3DMapper;
class vtkPolyData;
class vtkProperty;
class vtkCallbackCommand;
class vtkTNB;
class vtkCellLocator;
class vtkArrowSource;
class vtkTransform;

struct TNBPipeLineData;

class VTKINTERACTIONWIDGETS_EXPORT vtkOrientedGlyphContourRepresentationEx
  : public vtkOrientedGlyphContourRepresentation
{
public:
  static vtkOrientedGlyphContourRepresentationEx* New();
  vtkTypeMacro(vtkOrientedGlyphContourRepresentationEx, vtkOrientedGlyphContourRepresentation)
  void PrintSelf(std::ostream& os, vtkIndent indent) override;

  vtkSetMacro(ArrowScale, double);
  vtkGetMacro(ArrowScale, double);

  vtkSetMacro(ReductionFactor, double);
  vtkGetMacro(ReductionFactor, double);

  /**
   * Get the intermediate points in this contour as a vtkPolyData
   */
  void GetIntermediatePolyData(vtkPolyData* poly);

  void BuildRepresentation() override;

  ///@{
  /**
   */
  vtkGetObjectMacro(NormalsProperty, vtkProperty);
  vtkGetObjectMacro(BinormalsProperty, vtkProperty);
  vtkGetObjectMacro(TangentsProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Methods to make this class behave as a vtkProp.
   */
  void GetActors(vtkPropCollection*) override;
  void ReleaseGraphicsResources(vtkWindow*) override;
  int RenderOverlay(vtkViewport* viewport) override;
  int RenderOpaqueGeometry(vtkViewport* viewport) override;
  int RenderTranslucentPolygonalGeometry(vtkViewport* viewport) override;
  vtkTypeBool HasTranslucentPolygonalGeometry() override;
  ///@}

  void TNBCallback(vtkObject* caller, int ev);

  void TNBCallback(vtkObject* caller, const char* evString);

  void SetPolyData(vtkPolyData* polyData);

  void GetTransformAtIndex(vtkIdType index, vtkTransform* transform);

  class VTKINTERACTIONWIDGETS_EXPORT vtkInternals;

protected:
  double ArrowScale;
  double ReductionFactor;
  vtkActor* NormalsActor;
  vtkActor* TangentsActor;
  vtkActor* BinormalsActor;
  vtkPolyData* Normals;
  vtkPolyData* Tangents;
  vtkPolyData* Binormals;
  vtkGlyph3DMapper* NormalsMapper;
  vtkGlyph3DMapper* TangentsMapper;
  vtkGlyph3DMapper* BinormalsMapper;
  vtkProperty* NormalsProperty;
  vtkProperty* BinormalsProperty;
  vtkProperty* TangentsProperty;
  vtkTNB* Frame;
  vtkCellLocator* CellLocator;
  vtkPolyData* PolyData;
  vtkArrowSource* ArrowSource;

  vtkInternals* Internals;
  friend class vtkInternals;

private:
  vtkOrientedGlyphContourRepresentationEx();
  ~vtkOrientedGlyphContourRepresentationEx();
};

and the code here

#include <vtkCallbackCommand.h>
#include <vtkCellArray.h>
#include <vtkOrientedGlyphContourRepresentationEx.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>

// Option for interpolation, coarse/fine, show/hide, orientation for an index

// Test TNB Observer
#include <vtkArrowSource.h>
#include <vtkCellData.h>
#include <vtkCellLocator.h>
#include <vtkContourWidget.h>
#include <vtkGlyph3DMapper.h>
#include <vtkMaskPoints.h>
#include <vtkMatrix4x4.h>
#include <vtkPointData.h>
#include <vtkPointPlacer.h>
#include <vtkPolyDataCollection.h>
#include <vtkPolygonalSurfaceContourLineInterpolator.h>
#include <vtkPolygonalSurfacePointPlacer.h> // Is this used?
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSurfacePathInterpolator.h> // Using Dijkstra
#include <vtkTNB.h>
#include <vtkTransform.h>
#include <vtkVector.h>

vtkStandardNewMacro(vtkOrientedGlyphContourRepresentationEx);

class vtkOrientedGlyphContourRepresentationEx::vtkInternals
{
public:
  vtkInternals(vtkOrientedGlyphContourRepresentationEx* self)
    : Self(self)
  {
  }
  vtkOrientedGlyphContourRepresentationEx* Self;
  void Execute(vtkObject* caller, unsigned long vtkNotUsed(evId))
  {
    vtkContourWidget* cw = vtkContourWidget::SafeDownCast(caller);
    if (cw)
    {
      auto rep = vtkOrientedGlyphContourRepresentationEx::SafeDownCast(cw->GetRepresentation());
      if (rep)
      {
        // Cell normals are required!!!
        vtkPolyData* surfacePolyData = vtkPolyData::SafeDownCast(Self->PolyData);
        if (!surfacePolyData)
        {
          std::cerr << "No polydata associated with representation\n";
          return;
        }

        if (!surfacePolyData->GetCellData()->GetNormals())
        {
          std::cerr << "No cell normals defined for polydata\n";
          return;
        }
        vtkNew<vtkPolyData> pd;
        rep->GetIntermediatePolyData(pd);
        if (pd->GetNumberOfPoints() < 2)
        {
          return;
        }

        // Reset orientation
        Self->Frame->SetViewUp(0.0);
        Self->Frame->SetInputData(pd);

        // Input either masked or not
        vtkPolyDataAlgorithm* pipeLineInput;

        vtkNew<vtkMaskPoints> maskPoints;
        maskPoints->SetInputConnection(Self->Frame->GetOutputPort());

        int everyNth = Self->ReductionFactor == 0.0 ? 1.0 : int(1.0 / Self->ReductionFactor);

        maskPoints->SetOnRatio(everyNth);
        maskPoints->Update();

        // Masked input - every nth point
        pipeLineInput = maskPoints.GetPointer();

        // Tangent, normal and binormal for the first point.
        double *pNormal, *pTangent, *pBiNormal;

        pNormal = pipeLineInput->GetOutput()->GetPointData()->GetArray("FSNormals")->GetTuple(0);
        pBiNormal =
          pipeLineInput->GetOutput()->GetPointData()->GetArray("FSBinormals")->GetTuple(0);
        pTangent = pipeLineInput->GetOutput()->GetPointData()->GetArray("FSTangents")->GetTuple(0);

        double cellNormal[3];

        // Locate a normal on the surface near the first point
        vtkIdType cellId;
        int subId;
        double closestPoint[3];
        double distance = 0.0;

        Self->CellLocator->FindClosestPoint(pd->GetPoint(0), closestPoint, cellId, subId, distance);
        surfacePolyData->GetCellData()->GetNormals()->GetTuple(cellId, cellNormal);

        // Project cellNormal vector into the NxB plane and adjust N to align with cellNormal
        double UdotT = vtkMath::Dot(cellNormal, pTangent);
        for (int i = 0; i < 3; i++)
        {
          cellNormal[i] = cellNormal[i] - UdotT * pTangent[i];
        }

        vtkVector3d cellNormalMinusT = vtkVector3d(cellNormal);
        cellNormalMinusT.Normalize();

        double UdotN = vtkMath::Dot(cellNormalMinusT.GetData(), pNormal);
        double UdotB = vtkMath::Dot(cellNormalMinusT.GetData(), pBiNormal);

        // Angle in radians used for adjustment
        double angleAdjust = std::atan2(UdotB, UdotN);
        Self->Frame->SetViewUp(angleAdjust);

        pipeLineInput->SetInputConnection(Self->Frame->GetOutputPort());

        pipeLineInput->Update();

        // Normals
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSNormals");
        Self->Normals->DeepCopy(pipeLineInput->GetOutput());

        Self->NormalsMapper->SetInputData(Self->Normals);
        Self->NormalsMapper->SetScaleFactor(Self->ArrowScale);
        Self->NormalsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->NormalsMapper->Update();

        // Tangents
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSTangents");
        Self->Tangents->DeepCopy(pipeLineInput->GetOutput());
        Self->TangentsMapper->SetInputData(Self->Tangents);
        Self->TangentsMapper->SetScaleFactor(Self->ArrowScale);
        Self->TangentsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->TangentsMapper->Update();

        // Bi-normals
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSBinormals");
        Self->Binormals->DeepCopy(pipeLineInput->GetOutput());
        Self->BinormalsMapper->SetInputData(pipeLineInput->GetOutput());
        Self->BinormalsMapper->SetScaleFactor(Self->ArrowScale);
        Self->BinormalsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->BinormalsMapper->Update();
      }
    }
  }

private:
  double ReductionFactor;
};

void vtkOrientedGlyphContourRepresentationEx::SetPolyData(vtkPolyData* polydata)
{
  if (this->PolyData == polydata)
  {
    return;
  }
  if (this->PolyData)
  {
    this->PolyData->UnRegister(this);
  }
  this->PolyData = polydata;

  if (this->PolyData)
  {
    this->PolyData->Register(this);
    this->CellLocator->SetDataSet(this->PolyData);
    this->CellLocator->BuildLocator();
  }
}

//------------------------------------------------------------------------------
vtkOrientedGlyphContourRepresentationEx::vtkOrientedGlyphContourRepresentationEx()
{
  this->ArrowScale = 1.0;
  this->ReductionFactor = 0.0;
  this->Normals = vtkPolyData::New();
  this->NormalsMapper = vtkGlyph3DMapper::New();
  this->NormalsActor = vtkActor::New();
  this->NormalsActor->SetMapper(this->NormalsMapper);

  this->NormalsProperty = vtkProperty::New();
  this->NormalsProperty->SetColor(1.0, 0.0, 0.0);
  this->NormalsProperty->SetRepresentationToSurface();
  this->NormalsProperty->SetAmbient(1.0);
  this->NormalsProperty->SetDiffuse(0.0);
  this->NormalsProperty->SetSpecular(0.0);
  this->NormalsProperty->SetLineWidth(1.0);

  this->NormalsActor->SetProperty(this->NormalsProperty);

  this->Tangents = vtkPolyData::New();
  this->TangentsMapper = vtkGlyph3DMapper::New();
  this->TangentsActor = vtkActor::New();
  this->TangentsActor->SetMapper(this->TangentsMapper);

  this->TangentsProperty = vtkProperty::New();
  this->TangentsProperty->SetColor(0.0, 1.0, 0.0);
  this->TangentsProperty->SetRepresentationToSurface();
  this->TangentsProperty->SetAmbient(1.0);
  this->TangentsProperty->SetDiffuse(0.0);
  this->TangentsProperty->SetSpecular(0.0);
  this->TangentsProperty->SetLineWidth(1.0);

  this->TangentsActor->SetProperty(this->TangentsProperty);

  this->Binormals = vtkPolyData::New();
  this->BinormalsMapper = vtkGlyph3DMapper::New();
  this->BinormalsActor = vtkActor::New();
  this->BinormalsActor->SetMapper(this->BinormalsMapper);

  this->BinormalsProperty = vtkProperty::New();
  this->BinormalsProperty->SetColor(0.0, 0.0, 1.0);
  this->BinormalsProperty->SetRepresentationToSurface();
  this->BinormalsProperty->SetAmbient(1.0);
  this->BinormalsProperty->SetDiffuse(0.0);
  this->BinormalsProperty->SetSpecular(0.0);
  this->BinormalsProperty->SetLineWidth(1.0);

  this->BinormalsActor->SetProperty(this->BinormalsProperty);

  this->Frame = vtkTNB::New();
  this->Frame->ConsistentNormalsOn();
  this->CellLocator = vtkCellLocator::New();
  this->PolyData = nullptr;
  this->ArrowSource = vtkArrowSource::New();
  this->ArrowSource->SetTipResolution(16);
  this->ArrowSource->SetTipLength(.3);
  this->ArrowSource->SetTipRadius(.1);

  this->Internals = new vtkInternals(this);
}

//------------------------------------------------------------------------------
vtkOrientedGlyphContourRepresentationEx::~vtkOrientedGlyphContourRepresentationEx()
{
  this->Normals->Delete();
  this->NormalsMapper->Delete();
  this->NormalsActor->Delete();

  this->Tangents->Delete();
  this->TangentsMapper->Delete();
  this->TangentsActor->Delete();
  this->TangentsProperty->Delete();

  this->Binormals->Delete();
  this->BinormalsMapper->Delete();
  this->BinormalsActor->Delete();
  this->BinormalsProperty->Delete();

  this->ArrowSource->Delete();
  this->Frame->Delete();
  this->CellLocator->Delete();

  this->NormalsProperty->Delete();
  this->SetPolyData(nullptr);

  if (this->Internals)
  {
    delete this->Internals;
    this->Internals = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkOrientedGlyphContourRepresentationEx::PrintSelf(std::ostream& os, vtkIndent indent)
{
  Superclass::PrintSelf(os, indent);
  if (this->NormalsProperty)
  {
    os << indent << "Normals Property: " << this->NormalsProperty << "\n";
  }
  else
  {
    os << indent << "Normals Property: (none)\n";
  }
  if (this->BinormalsProperty)
  {
    os << indent << "Binormals Property: " << this->BinormalsProperty << "\n";
  }
  else
  {
    os << indent << "Binormals Property: (none)\n";
  }
  if (this->TangentsProperty)
  {
    os << indent << "Tangents Property: " << this->TangentsProperty << "\n";
  }
  else
  {
    os << indent << "Tangents Property: (none)\n";
  }
}

// vtkCommand* vtkOrientedGlyphContourRepresentationEx::GetTNBObserver()

//------------------------------------------------------------------------------
void vtkOrientedGlyphContourRepresentationEx::GetIntermediatePolyData(vtkPolyData* poly)
{
  poly->Initialize();
  int count = this->GetNumberOfNodes();

  if (count == 0)
  {
    return;
  }

  vtkNew<vtkPoints> points;
  vtkNew<vtkCellArray> lines;

  int i, j;
  vtkIdType index = 0;

  count = 0;
  for (i = 0; i < this->GetNumberOfNodes(); i++)
  {
    count += this->GetNumberOfIntermediatePoints(i);
  }
  points->SetNumberOfPoints(count);

  vtkIdType numLines;

  if (this->ClosedLoop && count > 0)
  {
    numLines = count + 1;
  }
  else
  {
    numLines = count;
  }

  if (numLines > 0)
  {
    vtkIdType* lineIndices = new vtkIdType[numLines];

    double pos[3];
    for (i = 0; i < this->GetNumberOfNodes(); i++)
    {
      int numIntermediatePoints = this->GetNumberOfIntermediatePoints(i);

      for (j = 0; j < numIntermediatePoints; j++)
      {
        Superclass::GetIntermediatePointWorldPosition(i, j, pos);
        points->InsertPoint(index, pos);
        if (index < numLines)
        {
          lineIndices[index] = index;
        }
        index++;
      }
    }

    if (Superclass::ClosedLoop)
    {
      if (index < numLines)
        lineIndices[index] = 0;
    }

    lines->InsertNextCell(numLines, lineIndices);
    delete[] lineIndices;
  }

  poly->SetPoints(points);
  poly->SetLines(lines);
}

void vtkOrientedGlyphContourRepresentationEx::TNBCallback(vtkObject* caller, int ev)
{
  this->Internals->Execute(caller, ev);
}

void vtkOrientedGlyphContourRepresentationEx::TNBCallback(vtkObject* caller, const char* evString)
{
  this->TNBCallback(caller, vtkCommand::GetEventIdFromString(evString));
}

void vtkOrientedGlyphContourRepresentationEx::BuildRepresentation()
{
  Superclass::BuildRepresentation();
}

int vtkOrientedGlyphContourRepresentationEx::RenderTranslucentPolygonalGeometry(
  vtkViewport* viewport)
{
  int count = Superclass::RenderTranslucentPolygonalGeometry(viewport);
  count += this->NormalsActor->RenderTranslucentPolygonalGeometry(viewport);
  count += this->BinormalsActor->RenderTranslucentPolygonalGeometry(viewport);
  count += this->TangentsActor->RenderTranslucentPolygonalGeometry(viewport);
  return count;
}

vtkTypeBool vtkOrientedGlyphContourRepresentationEx::HasTranslucentPolygonalGeometry()
{
  int result = Superclass::HasTranslucentPolygonalGeometry();
  if (this->NormalsActor->GetVisibility())
  {
    result |= this->NormalsActor->HasTranslucentPolygonalGeometry();
  }
  if (this->BinormalsActor->GetVisibility())
  {
    result |= this->BinormalsActor->HasTranslucentPolygonalGeometry();
  }
  if (this->TangentsActor->GetVisibility())
  {
    result |= this->TangentsActor->HasTranslucentPolygonalGeometry();
  }
  return result;
}

void vtkOrientedGlyphContourRepresentationEx::GetActors(vtkPropCollection* pc)
{
  Superclass::GetActors(pc);
  this->TangentsActor->GetActors(pc);
  this->NormalsActor->GetActors(pc);
  this->BinormalsActor->GetActors(pc);
}

int vtkOrientedGlyphContourRepresentationEx::RenderOverlay(vtkViewport* viewport)
{
  int count = Superclass::RenderOverlay(viewport);
  count += this->NormalsActor->RenderOverlay(viewport);
  count += this->BinormalsActor->RenderOverlay(viewport);
  count += this->TangentsActor->RenderOverlay(viewport);
  return count;
}

int vtkOrientedGlyphContourRepresentationEx::RenderOpaqueGeometry(vtkViewport* viewport)
{
  int count = Superclass::RenderOpaqueGeometry(viewport);
  count += this->NormalsActor->RenderOpaqueGeometry(viewport);
  count += this->BinormalsActor->RenderOpaqueGeometry(viewport);
  count += this->TangentsActor->RenderOpaqueGeometry(viewport);
  return count;
}

void vtkOrientedGlyphContourRepresentationEx::ReleaseGraphicsResources(vtkWindow* win)
{
  Superclass::ReleaseGraphicsResources(win);
  this->NormalsActor->ReleaseGraphicsResources(win);
  this->BinormalsActor->ReleaseGraphicsResources(win);
  this->TangentsActor->ReleaseGraphicsResources(win);
}

void vtkOrientedGlyphContourRepresentationEx::GetTransformAtIndex(
  vtkIdType index, vtkTransform* transform)
{
  vtkIdType nTransforms = this->Binormals->GetNumberOfPoints();
  if (index < nTransforms)
  {
    double* pBinormal = this->Binormals->GetPointData()->GetArray("FSBinormals")->GetTuple(index);
    double* pNormal = this->Binormals->GetPointData()->GetArray("FSNormals")->GetTuple(index);
    double* pTangent = this->Binormals->GetPointData()->GetArray("FSTangents")->GetTuple(index);
    double* pPosition = this->Binormals->GetPoint(index);

    vtkNew<vtkMatrix4x4> matrix;
    matrix->Identity();
    matrix->SetElement(0, 0, pBinormal[0]);
    matrix->SetElement(0, 1, pTangent[0]);
    matrix->SetElement(0, 2, pNormal[0]);
    matrix->SetElement(1, 0, pBinormal[1]);
    matrix->SetElement(1, 1, pTangent[1]);
    matrix->SetElement(1, 2, pNormal[1]);
    matrix->SetElement(2, 0, pBinormal[2]);
    matrix->SetElement(2, 1, pTangent[2]);
    matrix->SetElement(2, 2, pNormal[2]);
    matrix->SetElement(0, 3, pPosition[0]);
    matrix->SetElement(1, 3, pPosition[1]);
    matrix->SetElement(2, 3, pPosition[2]);

    transform->SetMatrix(matrix);
  }
}
1 Like