// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkRIBExporter.h"

#include "vtkAssemblyNode.h"
#include "vtkAssemblyPath.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkFieldData.h"
#include "vtkGeometryFilter.h"
#include "vtkImageAppendComponents.h"
#include "vtkImageConstantPad.h"
#include "vtkImageExtractComponents.h"
#include "vtkLightCollection.h"
#include "vtkMapper.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolygon.h"
#include "vtkRIBLight.h"
#include "vtkRIBProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRendererCollection.h"
#include "vtkStructuredPoints.h"
#include "vtkTIFFWriter.h"
#include "vtkTexture.h"
#include "vtkUnsignedCharArray.h"
#include "vtksys/SystemTools.hxx"

#include <sstream>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkRIBExporter);

typedef double RtColor[3];
typedef double RtPoint[3];
typedef char* RtPointer;
typedef float RtFloat;

vtkRIBExporter::vtkRIBExporter()
{
  this->FilePrefix = nullptr;
  this->FilePtr = nullptr;
  this->TexturePrefix = nullptr;
  this->Size[0] = this->Size[1] = -1;
  this->PixelSamples[0] = this->PixelSamples[1] = 2;
  this->Background = 0;
  this->ExportArrays = 0;
}

vtkRIBExporter::~vtkRIBExporter()
{
  delete[] this->FilePrefix;
  delete[] this->TexturePrefix;
}

void vtkRIBExporter::WriteData()
{
  // make sure the user specified a FilePrefix
  if (this->FilePrefix == nullptr)
  {
    vtkErrorMacro(<< "Please specify file name for the rib file");
    delete[] this->FilePrefix;
    delete[] this->TexturePrefix;
    return;
  }

  vtkActorCollection* ac;
  vtkLightCollection* lc;
  vtkActor* anActor;
  vtkLight* aLight;
  vtkTexture* aTexture;

  // get the renderer
  vtkRenderer* ren = this->ActiveRenderer;
  if (!ren)
  {
    ren = this->RenderWindow->GetRenderers()->GetFirstRenderer();
  }

  // make sure it has at least one actor
  if (ren->GetActors()->GetNumberOfItems() < 1)
  {
    vtkErrorMacro(<< "No actors found for writing .RIB file.");
    return;
  }

  size_t ribFileNameSize = strlen(this->FilePrefix) + strlen(".rib") + 1;
  char* ribFileName = new char[ribFileNameSize];
  snprintf(ribFileName, ribFileNameSize, "%s%s", this->FilePrefix, ".rib");

  this->FilePtr = vtksys::SystemTools::Fopen(ribFileName, "w");
  if (this->FilePtr == nullptr)
  {
    vtkErrorMacro(<< "Cannot open " << ribFileName);
    delete[] ribFileName;
    return;
  }

  delete[] ribFileName;

  //
  //  Write Header
  //
  this->WriteHeader(ren);

  //
  //  All textures must be made first
  //
  ac = ren->GetActors();
  vtkCollection* textures = vtkCollection::New();
  vtkCollectionSimpleIterator ait;
  for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait));)
  {
    // see if the actor has a mapper. it could be an assembly
    if (anActor->GetMapper() == nullptr)
    {
      continue;
    }
    // if it's invisible, don't make the texture
    if (anActor->GetVisibility())
    {
      aTexture = anActor->GetTexture();
      if (aTexture && textures->IndexOfFirstOccurence(aTexture) < 0)
      {
        this->WriteTexture(aTexture);
        textures->AddItem(aTexture);
      }
    }
  }

  //
  // Write viewport
  //
  this->WriteViewport(ren, this->Size);

  //
  // Write camera
  //
  this->WriteCamera(ren->GetActiveCamera());

  fprintf(this->FilePtr, "WorldBegin\n");

  //
  // Write all lights
  //
  lc = ren->GetLights();

  //
  // If there is no light defined, create one
  //
  vtkCollectionSimpleIterator sit;
  lc->InitTraversal(sit);
  if (lc->GetNextLight(sit) == nullptr)
  {
    vtkWarningMacro(<< "No light defined, creating one at camera position");
    ren->CreateLight();
  }

  // Create an ambient light
  this->WriteAmbientLight(1);
  int lightCount = 2;
  for (lc->InitTraversal(sit); (aLight = lc->GetNextLight(sit));)
  {
    if (aLight->GetSwitch())
    {
      this->WriteLight(aLight, lightCount++);
    }
  }

  //
  // Write all actors
  //
  vtkAssemblyNode* node;
  ac = ren->GetActors();
  vtkAssemblyPath* apath;
  for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait));)
  {
    for (anActor->InitPathTraversal(); (apath = anActor->GetNextPath());)
    {
      node = apath->GetLastNode();
      if (node->GetViewProp()->GetVisibility())
      {
        if (node->GetViewProp()->IsA("vtkActor"))
        {
          this->WriteActor((vtkActor*)(node->GetViewProp()));
        }
      }
    }
  }

  //  RiWorldEnd ();
  fprintf(this->FilePtr, "WorldEnd\n");
  //
  // Write trailer
  //
  this->WriteTrailer();

  //  RiEnd ();
  fclose(this->FilePtr);

  textures->Delete();
}

void vtkRIBExporter::WriteHeader(vtkRenderer* aRen)
{

  // create a FileName to hold the rendered image
  size_t length = strlen(this->FilePrefix) + strlen(".tif") + 1;
  char* imageFileName = new char[length];
  snprintf(imageFileName, length, "%s%s", this->FilePrefix, ".tif");

  fprintf(this->FilePtr, "FrameBegin %d\n", 1);
  fprintf(this->FilePtr, "Display \"%s\" \"file\" \"rgb\"\n", imageFileName);
  fprintf(this->FilePtr, "Declare \"color\" \"uniform color\"\n");
  if (this->Background)
  {
    double* color = aRen->GetBackground();
    fprintf(
      this->FilePtr, "Imager \"background\" \"color\" [%f %f %f]\n", color[0], color[1], color[2]);
  }
  fprintf(this->FilePtr, "PixelSamples %d %d\n", this->PixelSamples[0], this->PixelSamples[1]);

  delete[] imageFileName;
}

void vtkRIBExporter::WriteTrailer()
{
  fprintf(this->FilePtr, "FrameEnd\n");
}

void vtkRIBExporter::WriteProperty(vtkProperty* aProperty, vtkTexture* aTexture)
{
  char* mapName;
  double Ambient, Diffuse, Specular;
  double Opacity;
  double *DiffuseColor, *SpecularColor;
  double Roughness;
  RtColor opacity;
  Opacity = aProperty->GetOpacity();

  // set the opacity
  opacity[0] = Opacity;
  opacity[1] = Opacity;
  opacity[2] = Opacity;
  fprintf(this->FilePtr, "Opacity [%f %f %f]\n", opacity[0], opacity[1], opacity[2]);

  // set the color of the surface
  DiffuseColor = aProperty->GetDiffuseColor();
  fprintf(this->FilePtr, "Color [%f %f %f]\n", DiffuseColor[0], DiffuseColor[1], DiffuseColor[2]);

  // set the shader parameters
  Ambient = aProperty->GetAmbient();
  Diffuse = aProperty->GetDiffuse();
  Specular = aProperty->GetSpecular();

  SpecularColor = aProperty->GetSpecularColor();
  Roughness = (RtFloat)(1.0 / aProperty->GetSpecularPower());

  //
  // if there is a texture map we need to declare it
  //
  mapName = nullptr;
  if (aTexture)
  {
    mapName = this->GetTextureName(aTexture);
    if (mapName)
    {
      fprintf(this->FilePtr, "Declare \"texturename\" \"uniform string\"\n");
    }
  }
  //
  // Now we need to check to see if an RIBProperty has been specified
  //
  if (strcmp("vtkRIBProperty", aProperty->GetClassName()) == 0)
  {
    vtkRIBProperty* aRIBProperty = (vtkRIBProperty*)aProperty;
    if (aRIBProperty->GetDeclarations())
    {
      fprintf(this->FilePtr, "%s", aRIBProperty->GetDeclarations());
    }
    if (aRIBProperty->GetSurfaceShader())
    {
      fprintf(this->FilePtr, "%s \"%s\" ", "Surface", aRIBProperty->GetSurfaceShader());
      if (aRIBProperty->GetSurfaceShaderUsesDefaultParameters())
      {
        fprintf(this->FilePtr, "\"Ka\" [%f] ", Ambient);
        fprintf(this->FilePtr, "\"Kd\" [%f] ", Diffuse);
        fprintf(this->FilePtr, "\"Ks\" [%f] ", Specular);
        fprintf(this->FilePtr, "\"roughness\" [%f] ", Roughness);
        fprintf(this->FilePtr, "\"specularcolor\" [%f %f %f]\n", SpecularColor[0], SpecularColor[1],
          SpecularColor[2]);
        if (mapName)
        {
          fprintf(this->FilePtr, " \"texturename\" [\"%s\"]", mapName);
        }
      }
      if (aRIBProperty->GetSurfaceShaderParameters())
      {
        fprintf(this->FilePtr, "%s\n", aRIBProperty->GetSurfaceShaderParameters());
      }
    }
    if (aRIBProperty->GetDisplacementShader())
    {
      fprintf(this->FilePtr, "%s \"%s\" ", "Displacement", aRIBProperty->GetDisplacementShader());
      if (mapName)
      {
        fprintf(this->FilePtr, " \"texturename\" [\"%s\"]", mapName);
      }
      if (aRIBProperty->GetDisplacementShaderParameters())
      {
        fprintf(this->FilePtr, "%s", aRIBProperty->GetDisplacementShaderParameters());
      }
      fprintf(this->FilePtr, "\n");
    }
  }
  // Default Property
  else
  {
    fprintf(this->FilePtr, "Surface \"%s\" ", mapName ? "paintedplastic" : "plastic");
    fprintf(this->FilePtr, "\"Ka\" [%f] ", Ambient);
    fprintf(this->FilePtr, "\"Kd\" [%f] ", Diffuse);
    fprintf(this->FilePtr, "\"Ks\" [%f] ", Specular);
    fprintf(this->FilePtr, "\"roughness\" [%f] ", Roughness);
    fprintf(this->FilePtr, "\"specularcolor\" [%f %f %f] ", SpecularColor[0], SpecularColor[1],
      SpecularColor[2]);
    if (mapName)
    {
      fprintf(this->FilePtr, " \"texturename\" [\"%s\"]", mapName);
    }
    fprintf(this->FilePtr, "\n");
  }
}

void vtkRIBExporter::WriteLight(vtkLight* aLight, int count)
{
  double color[4];
  double* Color;
  double *Position, *FocalPoint;
  double Intensity;

  // get required info from light
  Intensity = aLight->GetIntensity();
  Color = aLight->GetDiffuseColor();
  color[0] = Color[0];
  color[1] = Color[1];
  color[2] = Color[2];
  color[3] = 1.0;

  FocalPoint = aLight->GetFocalPoint();
  Position = aLight->GetPosition();

  //
  // Now we need to check to see if an RIBLight has been specified
  //
  if (strcmp("vtkRIBLight", aLight->GetClassName()) == 0)
  {
    if (((vtkRIBLight*)aLight)->GetShadows())

    {
      fprintf(this->FilePtr, "Attribute \"light\" \"shadows\" \"on\"\n");
    }
  }
  // define the light source
  if (!aLight->GetPositional())
  {
    fprintf(this->FilePtr, "LightSource \"distantlight\" %d ", count);
    fprintf(this->FilePtr, "\"intensity\" [%f] ", Intensity);
    fprintf(this->FilePtr, "\"lightcolor\" [%f %f %f] ", color[0], color[1], color[2]);
    fprintf(this->FilePtr, "\"from\" [%f %f %f] ", Position[0], Position[1], Position[2]);
    fprintf(this->FilePtr, "\"to\" [%f %f %f]\n", FocalPoint[0], FocalPoint[1], FocalPoint[2]);
  }
  else
  {
    double coneAngle = aLight->GetConeAngle();
    double coneAngleRadians = vtkMath::RadiansFromDegrees(coneAngle);

    double exponent = aLight->GetExponent();
    fprintf(this->FilePtr, "LightSource \"spotlight\" %d ", count);
    fprintf(this->FilePtr, "\"intensity\" [%f] ", Intensity);
    fprintf(this->FilePtr, "\"lightcolor\" [%f %f %f] ", color[0], color[1], color[2]);
    fprintf(this->FilePtr, "\"from\" [%f %f %f] ", Position[0], Position[1], Position[2]);
    fprintf(this->FilePtr, "\"to\" [%f %f %f]\n", FocalPoint[0], FocalPoint[1], FocalPoint[2]);
    fprintf(this->FilePtr, "\"coneangle\" [%f]\n", coneAngleRadians);
    fprintf(this->FilePtr, "\"beamdistribution\" [%f]\n", exponent);
    fprintf(this->FilePtr, "\"conedeltaangle\" [%f]\n", 0.0);
  }
  if (strcmp("vtkRIBLight", aLight->GetClassName()) == 0)
  {
    if (((vtkRIBLight*)aLight)->GetShadows())
    {
      fprintf(this->FilePtr, "Attribute \"light\" \"shadows\" \"off\"\n");
    }
  }
}

void vtkRIBExporter::WriteAmbientLight(int count)
{
  fprintf(this->FilePtr, "LightSource \"ambientlight\" %d\n", count);
}

void vtkRIBExporter::WriteViewport(vtkRenderer* ren, int size[2])
{
  double aspect[2];
  double* vport;
  int left, right, bottom, top;

  if (size[0] != -1 || size[1] != -1)
  {
    vport = ren->GetViewport();

    left = (int)(vport[0] * (size[0] - 1));
    right = (int)(vport[2] * (size[0] - 1));

    bottom = (int)(vport[1] * (size[1] - 1));
    top = (int)(vport[3] * (size[1] - 1));

    fprintf(this->FilePtr, "Format %d %d 1\n", size[0], size[1]);

    fprintf(this->FilePtr, "CropWindow %f %f %f %f\n", vport[0], vport[2], vport[1], vport[3]);

    aspect[0] = (double)(right - left + 1) / (double)(top - bottom + 1);
    aspect[1] = 1.0;
    fprintf(this->FilePtr, "ScreenWindow %f %f %f %f\n", -aspect[0], aspect[0], -1.0, 1.0);
  }
}

static void PlaceCamera(FILE* filePtr, RtPoint, RtPoint, double);
static void AimZ(FILE* filePtr, RtPoint);

void vtkRIBExporter::WriteCamera(vtkCamera* aCamera)
{
  RtPoint direction;
  double position[3], focalPoint[3];

  aCamera->GetPosition(position);
  aCamera->GetFocalPoint(focalPoint);

  direction[0] = focalPoint[0] - position[0];
  direction[1] = focalPoint[1] - position[1];
  direction[2] = focalPoint[2] - position[2];
  vtkMath::Normalize(direction);

  RtFloat angle = aCamera->GetViewAngle();
  fprintf(this->FilePtr, "Projection \"perspective\" \"fov\" [%f]\n", angle);
  PlaceCamera(this->FilePtr, position, direction, aCamera->GetRoll());

  fprintf(this->FilePtr, "Orientation \"rh\"\n");
}

/*
 * PlaceCamera(): establish a viewpoint, viewing direction and orientation
 *    for a scene. This routine must be called before RiWorldBegin().
 *    position: a point giving the camera position
 *    direction: a point giving the camera direction relative to position
 *    roll: an optional rotation of the camera about its direction axis
 */

static double cameraMatrix[4][4] = { { -1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 },
  { 0, 0, 0, 1 } };

void PlaceCamera(FILE* filePtr, RtPoint position, RtPoint direction, double roll)
{
  fprintf(filePtr, "Identity\n");
  fprintf(filePtr, "Transform [%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]\n",
    cameraMatrix[0][0], cameraMatrix[0][1], cameraMatrix[0][2], cameraMatrix[0][3],
    cameraMatrix[1][0], cameraMatrix[1][1], cameraMatrix[1][2], cameraMatrix[1][3],
    cameraMatrix[2][0], cameraMatrix[2][1], cameraMatrix[2][2], cameraMatrix[2][3],
    cameraMatrix[3][0], cameraMatrix[3][1], cameraMatrix[3][2], cameraMatrix[3][3]);

  fprintf(filePtr, "Rotate %f %f %f %f\n", -roll, 0.0, 0.0, 1.0);
  AimZ(filePtr, direction);
  fprintf(filePtr, "Translate %f %f %f\n", -position[0], -position[1], -position[2]);
}

/*
 * AimZ(): rotate the world so the directionvector points in
 *    positive z by rotating about the y axis, then x. The cosine
 *    of each rotation is given by components of the normalized
 *    direction vector. Before the y rotation the direction vector
 *    might be in negative z, but not afterward.
 */

static void AimZ(FILE* filePtr, RtPoint direction)
{
  double xzlen, yzlen, yrot, xrot;

  if (direction[0] == 0 && direction[1] == 0 && direction[2] == 0)
  {
    return;
  }
  /*
   * The initial rotation about the y axis is given by the projection of
   * the direction vector onto the x,z plane: the x and z components
   * of the direction.
   */
  xzlen = sqrt(direction[0] * direction[0] + direction[2] * direction[2]);
  if (xzlen == 0)
  {
    yrot = (direction[1] < 0) ? 180 : 0;
  }
  else
  {
    yrot = 180 * acos(direction[2] / xzlen) / vtkMath::Pi();
  }
  /*
   * The second rotation, about the x axis, is given by the projection on
   * the y,z plane of the y-rotated direction vector: the original y
   * component, and the rotated x,z vector from above.
   */
  yzlen = sqrt(direction[1] * direction[1] + xzlen * xzlen);
  xrot = 180 * acos(xzlen / yzlen) / vtkMath::Pi(); /* yzlen should never be 0 */

  if (direction[1] > 0)
  {
    fprintf(filePtr, "Rotate %f %f %f %f\n", xrot, 1.0, 0.0, 0.0);
  }
  else
  {
    fprintf(filePtr, "Rotate %f %f %f %f\n", -xrot, 1.0, 0.0, 0.0);
  }
  /* The last rotation declared gets performed first */
  if (direction[0] > 0)
  {
    fprintf(filePtr, "Rotate %f %f %f %f\n", -yrot, 0.0, 1.0, 0.0);
  }
  else
  {
    fprintf(filePtr, "Rotate %f %f %f %f\n", yrot, 0.0, 1.0, 0.0);
  }
}

void vtkRIBExporter::WriteActor(vtkActor* anActor)
{
  vtkDataSet* aDataSet;
  vtkPolyData* polyData;
  vtkGeometryFilter* geometryFilter = nullptr;
  vtkMatrix4x4* matrix = vtkMatrix4x4::New();

  // see if the actor has a mapper. it could be an assembly
  if (anActor->GetMapper() == nullptr)
  {
    return;
  }

  fprintf(this->FilePtr, "AttributeBegin\n");

  fprintf(this->FilePtr, "TransformBegin\n");

  // write out the property
  this->WriteProperty(anActor->GetProperty(), anActor->GetTexture());

  // get the mappers input and matrix
  aDataSet = anActor->GetMapper()->GetInput();
  anActor->GetMatrix(matrix);
  matrix->Transpose();

  // insert model transformation
  fprintf(this->FilePtr, "ConcatTransform [%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]\n",
    matrix->Element[0][0], matrix->Element[0][1], matrix->Element[0][2], matrix->Element[0][3],
    matrix->Element[1][0], matrix->Element[1][1], matrix->Element[1][2], matrix->Element[1][3],
    matrix->Element[2][0], matrix->Element[2][1], matrix->Element[2][2], matrix->Element[2][3],
    matrix->Element[3][0], matrix->Element[3][1], matrix->Element[3][2], matrix->Element[3][3]);

  // we really want polydata
  if (aDataSet->GetDataObjectType() != VTK_POLY_DATA)
  {
    geometryFilter = vtkGeometryFilter::New();
    geometryFilter->SetInputConnection(anActor->GetMapper()->GetInputConnection(0, 0));
    geometryFilter->Update();
    polyData = geometryFilter->GetOutput();
  }
  else
  {
    polyData = (vtkPolyData*)aDataSet;
  }

  // Let us start with point data and then we can copy to other
  if (this->ExportArrays)
  {
    vtkPointData* pointData = polyData->GetPointData();
    if (pointData && pointData->GetNumberOfArrays())
    {
      int cc;
      for (cc = 0; cc < pointData->GetNumberOfArrays(); cc++)
      {
        vtkDataArray* array = pointData->GetArray(cc);
        char buffer[1024];
        this->ModifyArrayName(buffer, array->GetName());
        fprintf(this->FilePtr, "Declare \"%s\" \"varying double\"\n", buffer);
      }
    }
    vtkCellData* cellData = polyData->GetCellData();
    if (cellData && cellData->GetNumberOfArrays())
    {
      int cc;
      for (cc = 0; cc < cellData->GetNumberOfArrays(); cc++)
      {
        vtkDataArray* array = cellData->GetArray(cc);
        char buffer[1024];
        this->ModifyArrayName(buffer, array->GetName());
        fprintf(this->FilePtr, "Declare \"%s\" \"varying double\"\n", buffer);
      }
    }
    vtkFieldData* fieldData = polyData->GetFieldData();
    if (fieldData && fieldData->GetNumberOfArrays())
    {
      int cc;
      for (cc = 0; cc < fieldData->GetNumberOfArrays(); cc++)
      {
        vtkDataArray* array = fieldData->GetArray(cc);
        char buffer[1024];
        this->ModifyArrayName(buffer, array->GetName());
        fprintf(this->FilePtr, "Declare \"%s\" \"varying double\"\n", buffer);
      }
    }
  }

  if (polyData->GetNumberOfPolys())
  {
    this->WritePolygons(polyData, anActor->GetMapper()->MapScalars(1.0), anActor->GetProperty());
  }
  if (polyData->GetNumberOfStrips())
  {
    this->WriteStrips(polyData, anActor->GetMapper()->MapScalars(1.0), anActor->GetProperty());
  }
  fprintf(this->FilePtr, "TransformEnd\n");
  fprintf(this->FilePtr, "AttributeEnd\n");
  if (geometryFilter)
  {
    geometryFilter->Delete();
  }
  matrix->Delete();
}

void vtkRIBExporter::WritePolygons(
  vtkPolyData* polyData, vtkUnsignedCharArray* c, vtkProperty* aProperty)
{
  double vertexColors[512][3];
  double* TCoords;
  double* normals;
  double points[3];
  RtPoint vertexNormals[512];
  RtPoint vertexPoints[512];
  double poly_norm[3];
  double vertexTCoords[512][2];
  const vtkIdType* pts = nullptr;
  vtkIdType npts = 0;
  int k, kk;
  int rep, j, interpolation;
  int tDim;
  unsigned char* colors;
  vtkCellArray* polys;
  vtkDataArray* n = nullptr;
  vtkPoints* p;
  vtkPolygon* polygon;
  vtkDataArray* t;

  // get the representation
  rep = aProperty->GetRepresentation();

  switch (rep)
  {
    case VTK_SURFACE:
      break;
    default:
      vtkErrorMacro(<< "Bad representation. Only Surface is supported.");
      break;
  }

  // get the shading interpolation
  interpolation = aProperty->GetInterpolation();

  // and draw the display list
  polygon = vtkPolygon::New();
  p = polyData->GetPoints();
  polys = polyData->GetPolys();

  t = polyData->GetPointData()->GetTCoords();
  if (t)
  {
    tDim = t->GetNumberOfComponents();
    if (tDim != 2)
    {
      vtkDebugMacro(<< "Currently only 2d textures are supported.\n");
      t = nullptr;
    }
  }

  // Get point data
  vtkPointData* pointData = polyData->GetPointData();
  vtkCellData* cellData = polyData->GetCellData();
  vtkFieldData* fieldData = polyData->GetFieldData();

  if (interpolation == VTK_FLAT || !(polyData->GetPointData()) ||
    !(n = polyData->GetPointData()->GetNormals()))
  {
    n = nullptr;
  }

  for (polys->InitTraversal(); polys->GetNextCell(npts, pts);)
  {
    if (!n)
    {
      vtkPolygon::ComputeNormal(p, npts, pts, poly_norm);
    }

    for (j = 0; j < npts; j++)
    {
      k = j;
      if (c)
      {
        colors = c->GetPointer(4 * pts[k]);
        vertexColors[k][0] = colors[0] / 255.0;
        vertexColors[k][1] = colors[1] / 255.0;
        vertexColors[k][2] = colors[2] / 255.0;
      }
      if (t)
      {
        TCoords = t->GetTuple(pts[k]);
        vertexTCoords[k][0] = TCoords[0];
        // Renderman Textures have origin at upper left
        vertexTCoords[k][1] = 1.0 - TCoords[1];
      }
      if (n)
      {
        normals = n->GetTuple(pts[k]);
        vertexNormals[k][0] = normals[0];
        vertexNormals[k][1] = normals[1];
        vertexNormals[k][2] = normals[2];
      }
      else
      {
        vertexNormals[k][0] = poly_norm[0];
        vertexNormals[k][1] = poly_norm[1];
        vertexNormals[k][2] = poly_norm[2];
      }

      p->GetPoint(pts[k], points);
      vertexPoints[k][0] = points[0];
      vertexPoints[k][1] = points[1];
      vertexPoints[k][2] = points[2];
    }
    fprintf(this->FilePtr, "Polygon ");
    fprintf(this->FilePtr, "\"P\" [");
    for (kk = 0; kk < npts; kk++)
    {
      fprintf(
        this->FilePtr, "%f %f %f ", vertexPoints[kk][0], vertexPoints[kk][1], vertexPoints[kk][2]);
    }
    fprintf(this->FilePtr, "] ");

    fprintf(this->FilePtr, "\"N\" [");
    for (kk = 0; kk < npts; kk++)
    {
      fprintf(this->FilePtr, "%f %f %f ", vertexNormals[kk][0], vertexNormals[kk][1],
        vertexNormals[kk][2]);
    }
    fprintf(this->FilePtr, "] ");

    if (c)
    {
      fprintf(this->FilePtr, "\"Cs\" [");
      for (kk = 0; kk < npts; kk++)
      {
        fprintf(this->FilePtr, "%f %f %f ", vertexColors[kk][0], vertexColors[kk][1],
          vertexColors[kk][2]);
      }
      fprintf(this->FilePtr, "] ");
    }
    if (t)
    {
      fprintf(this->FilePtr, "\"st\" [");
      for (kk = 0; kk < npts; kk++)
      {
        fprintf(this->FilePtr, "%f %f ", vertexTCoords[kk][0], vertexTCoords[kk][1]);
      }
      fprintf(this->FilePtr, "] ");
    }

    if (this->ExportArrays)
    {
      if (pointData)
      {
        int cc, aa;
        std::ostringstream str_with_warning_C4701;
        for (cc = 0; cc < pointData->GetNumberOfArrays(); cc++)
        {
          vtkDataArray* array = pointData->GetArray(cc);
          char buffer[1024];
          this->ModifyArrayName(buffer, array->GetName());
          str_with_warning_C4701 << "\"" << buffer << "\" [";
          for (kk = 0; kk < npts; kk++)
          {
            double tuple[3];
            array->GetTuple(pts[kk], tuple);
            for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
            {
              str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
            }
          }
          str_with_warning_C4701 << "] ";
        }
        fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
      }

      if (cellData)
      {
        int cc, aa;
        std::ostringstream str_with_warning_C4701;
        for (cc = 0; cc < cellData->GetNumberOfArrays(); cc++)
        {
          vtkDataArray* array = cellData->GetArray(cc);
          char buffer[1024];
          this->ModifyArrayName(buffer, array->GetName());
          str_with_warning_C4701 << "\"" << buffer << "\" [";
          for (kk = 0; kk < npts; kk++)
          {
            double tuple[3];
            array->GetTuple(pts[kk], tuple);
            for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
            {
              str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
            }
          }
          str_with_warning_C4701 << "] ";
        }
        fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
      }

      if (fieldData)
      {
        int cc, aa;
        std::ostringstream str_with_warning_C4701;

        for (cc = 0; cc < fieldData->GetNumberOfArrays(); cc++)
        {
          vtkDataArray* array = fieldData->GetArray(cc);
          char buffer[1024];
          this->ModifyArrayName(buffer, array->GetName());
          str_with_warning_C4701 << "\"" << buffer << "\" [";
          for (kk = 0; kk < npts; kk++)
          {
            double tuple[3];
            array->GetTuple(pts[kk], tuple);
            for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
            {
              str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
            }
          }
          str_with_warning_C4701 << "] ";
        }
        fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
      }
    }

    fprintf(this->FilePtr, "\n");
  }
  polygon->Delete();
}

void vtkRIBExporter::WriteStrips(
  vtkPolyData* polyData, vtkUnsignedCharArray* c, vtkProperty* aProperty)
{
  double vertexColors[512][3];
  double* TCoords;
  double* normals;
  double points[3];
  RtPoint vertexNormals[512];
  RtPoint vertexPoints[512];
  double poly_norm[3];
  double vertexTCoords[512][2];
  const vtkIdType* pts = nullptr;
  vtkIdType npts = 0;
  int p1, p2, p3;
  int k, kk;
  int rep, j, interpolation;
  int tDim;
  unsigned char* colors;
  vtkCellArray* strips;
  vtkDataArray* n = nullptr;
  vtkPoints* p;
  vtkDataArray* t;
  vtkPolygon* polygon;
  vtkIdType idx[3];

  // get the representation
  rep = aProperty->GetRepresentation();

  switch (rep)
  {
    case VTK_SURFACE:
      break;
    default:
      vtkErrorMacro(<< "Bad representation. Only Surface is supported.");
      break;
  }

  // get the shading interpolation
  interpolation = aProperty->GetInterpolation();

  // and draw the display list
  p = polyData->GetPoints();
  strips = polyData->GetStrips();
  polygon = vtkPolygon::New();

  t = polyData->GetPointData()->GetTCoords();
  if (t)
  {
    tDim = t->GetNumberOfComponents();
    if (tDim != 2)
    {
      vtkDebugMacro(<< "Currently only 2d textures are supported.\n");
      t = nullptr;
    }
  }

  if (interpolation == VTK_FLAT || !(polyData->GetPointData()) ||
    !(n = polyData->GetPointData()->GetNormals()))
  {
    n = nullptr;
  }

  // Get point data
  vtkPointData* pointData = polyData->GetPointData();
  vtkCellData* cellData = polyData->GetCellData();
  vtkFieldData* fieldData = polyData->GetFieldData();

  // each iteration returns a triangle strip
  for (strips->InitTraversal(); strips->GetNextCell(npts, pts);)
  {
    // each triangle strip is converted into a bunch of triangles
    p1 = pts[0];
    p2 = pts[1];
    p3 = pts[2];
    for (j = 0; j < (npts - 2); j++)
    {
      if (j % 2)
      {
        idx[0] = p2;
        idx[1] = p1;
        idx[2] = p3;
      }
      else
      {
        idx[0] = p1;
        idx[1] = p2;
        idx[2] = p3;
      }

      if (!n)
      {
        vtkPolygon::ComputeNormal(p, 3, idx, poly_norm);
      }

      // build colors, texture coordinates and normals for the triangle
      for (k = 0; k < 3; k++)
      {
        if (c)
        {
          colors = c->GetPointer(4 * idx[k]);
          vertexColors[k][0] = colors[0] / 255.0;
          vertexColors[k][1] = colors[1] / 255.0;
          vertexColors[k][2] = colors[2] / 255.0;
        }
        if (t)
        {
          TCoords = t->GetTuple(idx[k]);
          vertexTCoords[k][0] = TCoords[0];
          // Renderman Textures have origin at upper left
          vertexTCoords[k][1] = 1.0 - TCoords[1];
        }
        if (n)
        {
          normals = n->GetTuple(idx[k]);
          vertexNormals[k][0] = normals[0];
          vertexNormals[k][1] = normals[1];
          vertexNormals[k][2] = normals[2];
        }
        else
        {
          vertexNormals[k][0] = poly_norm[0];
          vertexNormals[k][1] = poly_norm[1];
          vertexNormals[k][2] = poly_norm[2];
        }
        p->GetPoint(idx[k], points);
        vertexPoints[k][0] = points[0];
        vertexPoints[k][1] = points[1];
        vertexPoints[k][2] = points[2];
      }
      fprintf(this->FilePtr, "Polygon ");
      fprintf(this->FilePtr, "\"P\" [");
      for (kk = 0; kk < 3; kk++)
      {
        fprintf(this->FilePtr, "%f %f %f ", vertexPoints[kk][0], vertexPoints[kk][1],
          vertexPoints[kk][2]);
      }
      fprintf(this->FilePtr, "] ");

      fprintf(this->FilePtr, "\"N\" [");
      for (kk = 0; kk < 3; kk++)
      {
        fprintf(this->FilePtr, "%f %f %f ", vertexNormals[kk][0], vertexNormals[kk][1],
          vertexNormals[kk][2]);
      }
      fprintf(this->FilePtr, "] ");

      if (c)
      {
        fprintf(this->FilePtr, "\"Cs\" [");
        for (kk = 0; kk < 3; kk++)
        {
          fprintf(this->FilePtr, "%f %f %f ", vertexColors[kk][0], vertexColors[kk][1],
            vertexColors[kk][2]);
        }
        fprintf(this->FilePtr, "] ");
      }
      if (t)
      {
        fprintf(this->FilePtr, "\"st\" [");
        for (kk = 0; kk < 3; kk++)
        {
          fprintf(this->FilePtr, "%f %f ", vertexTCoords[kk][0], vertexTCoords[kk][1]);
        }
        fprintf(this->FilePtr, "] ");
      }

      if (this->ExportArrays)
      {
        if (pointData)
        {
          int cc, aa;
          std::ostringstream str_with_warning_C4701;
          for (cc = 0; cc < pointData->GetNumberOfArrays(); cc++)
          {
            vtkDataArray* array = pointData->GetArray(cc);
            char buffer[1024];
            this->ModifyArrayName(buffer, array->GetName());
            str_with_warning_C4701 << "\"" << buffer << "\" [";
            for (kk = 0; kk < npts; kk++)
            {
              double tuple[3];
              array->GetTuple(pts[kk], tuple);
              for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
              {
                str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
              }
            }
            str_with_warning_C4701 << "] ";
          }
          fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
        }

        if (cellData)
        {
          int cc, aa;
          std::ostringstream str_with_warning_C4701;
          for (cc = 0; cc < cellData->GetNumberOfArrays(); cc++)
          {
            vtkDataArray* array = cellData->GetArray(cc);
            char buffer[1024];
            this->ModifyArrayName(buffer, array->GetName());
            str_with_warning_C4701 << "\"" << buffer << "\" [";
            for (kk = 0; kk < npts; kk++)
            {
              double tuple[3];
              array->GetTuple(pts[kk], tuple);
              for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
              {
                str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
              }
            }
            str_with_warning_C4701 << "] ";
          }
          fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
        }

        if (fieldData)
        {
          int cc, aa;
          std::ostringstream str_with_warning_C4701;
          for (cc = 0; cc < fieldData->GetNumberOfArrays(); cc++)
          {
            vtkDataArray* array = fieldData->GetArray(cc);
            char buffer[1024];
            this->ModifyArrayName(buffer, array->GetName());
            str_with_warning_C4701 << "\"" << buffer << "\" [";
            for (kk = 0; kk < npts; kk++)
            {
              double tuple[3];
              array->GetTuple(pts[kk], tuple);
              for (aa = 0; aa < array->GetNumberOfComponents(); aa++)
              {
                str_with_warning_C4701 << ((!kk && !aa) ? "" : " ") << tuple[aa];
              }
            }
            str_with_warning_C4701 << "] ";
          }
          fprintf(this->FilePtr, "%s", str_with_warning_C4701.str().c_str());
        }
      }
      fprintf(this->FilePtr, "\n");
      // Get ready for next triangle
      p1 = p2;
      p2 = p3;
      if (j + 3 < npts)
      {
        p3 = pts[j + 3];
      }
    }
  }
  polygon->Delete();
}

void vtkRIBExporter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  if (this->FilePrefix)
  {
    os << indent << "FilePrefix: " << this->FilePrefix << "\n";
  }
  else
  {
    os << indent << "FilePrefix: (none)\n";
  }
  if (this->TexturePrefix)
  {
    os << indent << "TexturePrefix: " << this->TexturePrefix << "\n";
  }
  else
  {
    os << indent << "TexturePrefix: (none)\n";
  }
  os << indent << "Background: " << (this->Background ? "On\n" : "Off\n");
  os << indent << "Size: " << this->Size[0] << " " << this->Size[1] << "\n";
  os << indent << "PixelSamples: " << this->PixelSamples[0] << " " << this->PixelSamples[1] << "\n";
  os << indent << "Export Arrays: " << (this->ExportArrays ? "On" : "Off") << "\n";
}

void vtkRIBExporter::WriteTexture(vtkTexture* aTexture)
{
  vtkDataArray* scalars;
  vtkDataArray* mappedScalars;
  int* size;
  int xsize, ysize;
  unsigned short xs, ys;

  //    RtToken wrap = aTexture->GetRepeat () ? RI_PERIODIC : RI_CLAMP;
  //    RiMakeTexture (this->GetTIFFName (aTexture),
  //                 this->GetTextureName (aTexture),
  //                   wrap, wrap,
  //                   RiBoxFilter,
  //                   1, 1,
  //                   RI_NULL);
  const char* wrap = aTexture->GetRepeat() ? "periodic" : "clamp";
  fprintf(this->FilePtr, "MakeTexture \"%s\" ", this->GetTIFFName(aTexture));
  fprintf(this->FilePtr, "\"%s\" ", this->GetTextureName(aTexture));
  fprintf(this->FilePtr, "\"%s\" \"%s\" ", wrap, wrap);
  fprintf(this->FilePtr, "\"%s\" 1 1\n", "box");

  // do an Update and get some info
  if (aTexture->GetInput() == nullptr)
  {
    vtkErrorMacro(<< "texture has no input!\n");
    return;
  }
  aTexture->Update();
  size = aTexture->GetInput()->GetDimensions();
  scalars = aTexture->GetInput()->GetPointData()->GetScalars();

  // make sure scalars are non null
  if (!scalars)
  {
    vtkErrorMacro(<< "No scalar values found for texture input!\n");
    return;
  }

  // make sure using unsigned char data of color scalars type
  if (aTexture->GetColorMode() == VTK_COLOR_MODE_MAP_SCALARS ||
    (scalars->GetDataType() != VTK_UNSIGNED_CHAR))
  {
    mappedScalars = aTexture->GetMappedScalars();
  }
  else
  {
    mappedScalars = scalars;
  }

  // we only support 2d texture maps right now
  // so one of the three sizes must be 1, but it
  // could be any of them, so lets find it
  if (size[0] == 1)
  {
    xsize = size[1];
    ysize = size[2];
  }
  else
  {
    xsize = size[0];
    if (size[1] == 1)
    {
      ysize = size[2];
    }
    else
    {
      ysize = size[1];
      if (size[2] != 1)
      {
        vtkErrorMacro(<< "3D texture maps currently are not supported!\n");
        return;
      }
    }
  }

  // xsize and ysize must be a power of 2 in OpenGL
  xs = (unsigned short)xsize;
  ys = (unsigned short)ysize;
  while (!(xs & 0x01))
  {
    xs = xs >> 1;
  }
  while (!(ys & 0x01))
  {
    ys = ys >> 1;
  }
  if ((xs > 1) || (ys > 1))
  {
    vtkWarningMacro(<< "Texture map's width and height must be a power of two in RenderMan\n");
  }

  vtkTIFFWriter* aWriter = vtkTIFFWriter::New();
  vtkImageConstantPad* icp = nullptr;
  vtkImageExtractComponents* iec = nullptr;
  vtkImageAppendComponents* iac1 = nullptr;
  vtkImageAppendComponents* iac2 = nullptr;

  vtkStructuredPoints* anImage = vtkStructuredPoints::New();
  anImage->SetDimensions(xsize, ysize, 1);
  anImage->GetPointData()->SetScalars(mappedScalars);
  int bpp = mappedScalars->GetNumberOfComponents();

  // renderman and bmrt seem to require r,g,b and alpha in all their
  // texture maps. So if our tmap doesn't have the right components
  // we add them
  if (bpp == 1) // needs intensity intensity and alpha
  {
    iac1 = vtkImageAppendComponents::New();
    iac2 = vtkImageAppendComponents::New();
    icp = vtkImageConstantPad::New();

    iac1->SetInputData(0, anImage);
    iac1->SetInputData(1, anImage);
    iac2->SetInputConnection(0, iac1->GetOutputPort());
    iac2->SetInputData(1, anImage);
    icp->SetInputConnection(iac2->GetOutputPort());
    icp->SetConstant(255);
    icp->SetOutputNumberOfScalarComponents(4);

    aWriter->SetInputConnection(icp->GetOutputPort());
  }
  else if (bpp == 2) // needs intensity intensity
  {
    iec = vtkImageExtractComponents::New();
    iac1 = vtkImageAppendComponents::New();
    iac2 = vtkImageAppendComponents::New();

    iec->SetInputData(anImage);
    iec->SetComponents(0);
    iac1->SetInputConnection(0, iec->GetOutputPort());
    iac1->SetInputData(1, anImage);
    iac2->SetInputConnection(0, iec->GetOutputPort());
    iac2->SetInputConnection(1, iac1->GetOutputPort());

    aWriter->SetInputConnection(iac2->GetOutputPort());
  }
  else if (bpp == 3) // needs alpha
  {
    icp = vtkImageConstantPad::New();
    icp->SetInputData(anImage);
    icp->SetConstant(255);
    icp->SetOutputNumberOfScalarComponents(4);
    aWriter->SetInputConnection(icp->GetOutputPort());
  }
  else // needs nothing
  {
    aWriter->SetInputData(anImage);
  }
  aWriter->SetFileName(this->GetTIFFName(aTexture));
  aWriter->Write();

  if (bpp == 1)
  {
    iac1->Delete();
    iac2->Delete();
    icp->Delete();
  }
  else if (bpp == 2)
  {
    iec->Delete();
    iac1->Delete();
    iac2->Delete();
  }
  else if (bpp == 3)
  {
    icp->Delete();
  }

  aWriter->Delete();
  anImage->Delete();
}

static char tiffName[4096];
static char textureName[4096];

char* vtkRIBExporter::GetTIFFName(vtkTexture* aTexture)
{
  snprintf(tiffName, 4096, "%s_%p_%d.tif", this->TexturePrefix, (void*)aTexture,
    (int)aTexture->GetMTime());
  return tiffName;
}

char* vtkRIBExporter::GetTextureName(vtkTexture* aTexture)
{
  snprintf(textureName, 4096, "%s_%p_%d.txt", this->TexturePrefix, (void*)aTexture,
    (int)aTexture->GetMTime());
  return textureName;
}

void vtkRIBExporter::ModifyArrayName(char* newname, const char* name)
{
  if (!newname)
  {
    return;
  }
  if (!name)
  {
    *newname = 0;
    return;
  }
  int cc = 0;
  for (cc = 0; name[cc]; cc++)
  {
    if ((name[cc] >= 'A' && name[cc] <= 'Z') || (name[cc] >= '0' && name[cc] <= '9') ||
      (name[cc] >= 'a' && name[cc] <= 'z'))
    {
      newname[cc] = name[cc];
    }
    else
    {
      newname[cc] = '_';
    }
  }
  newname[cc] = 0;
}
VTK_ABI_NAMESPACE_END
