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

#include "vtkActor2D.h"
#include "vtkActor2DCollection.h"
#include "vtkAssemblyPath.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkGeometryFilter.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkLight.h"
#include "vtkLightCollection.h"
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyDataMapper2D.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRendererCollection.h"
#include "vtkSmartPointer.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkTexture.h"
#include "vtkTransform.h"
#include "vtkUnsignedCharArray.h"
#include "vtkX3D.h"
#include "vtkX3DExporterFIWriter.h"
#include "vtkX3DExporterXMLWriter.h"

#include <cassert>
#include <sstream>

using namespace vtkX3D;

// forward declarations
VTK_ABI_NAMESPACE_BEGIN
static bool vtkX3DExporterWriterUsingCellColors(vtkMapper* mapper);
static bool vtkX3DExporterWriterRenderFaceSet(int cellType, int representation, vtkPoints* points,
  vtkIdType cellOffset, vtkCellArray* cells, vtkUnsignedCharArray* colors, bool cell_colors,
  vtkDataArray* normals, bool cell_normals, vtkDataArray* tcoords, bool common_data_written,
  int index, vtkX3DExporterWriter* writer);
static void vtkX3DExporterWriteData(vtkPoints* points, vtkDataArray* normals, vtkDataArray* tcoords,
  vtkUnsignedCharArray* colors, int index, vtkX3DExporterWriter* writer);
static void vtkX3DExporterUseData(
  bool normals, bool tcoords, bool colors, int index, vtkX3DExporterWriter* writer);
static bool vtkX3DExporterWriterRenderVerts(vtkPoints* points, vtkCellArray* cells,
  vtkUnsignedCharArray* colors, bool cell_colors, vtkX3DExporterWriter* writer);
static bool vtkX3DExporterWriterRenderPoints(
  vtkPolyData* pd, vtkUnsignedCharArray* colors, bool cell_colors, vtkX3DExporterWriter* writer);

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkX3DExporter);

//------------------------------------------------------------------------------
vtkX3DExporter::vtkX3DExporter()
{
  this->Speed = 4.0;
  this->FileName = nullptr;
  this->Binary = 0;
  this->Fastest = 0;
  this->WriteToOutputString = 0;
  this->OutputString = nullptr;
  this->OutputStringLength = 0;
}

//------------------------------------------------------------------------------
vtkX3DExporter::~vtkX3DExporter()
{
  this->SetFileName(nullptr);
  delete[] this->OutputString;
}

//------------------------------------------------------------------------------
void vtkX3DExporter::WriteData()
{
  vtkSmartPointer<vtkX3DExporterWriter> writer;
  vtkActorCollection* ac;
  vtkActor2DCollection* a2Dc;
  vtkActor *anActor, *aPart;
  vtkActor2D *anTextActor2D, *aPart2D;
  vtkLightCollection* lc;
  vtkLight* aLight;
  vtkCamera* cam;

  // make sure the user specified a FileName or FilePointer
  if (this->FileName == nullptr && (!this->WriteToOutputString))
  {
    vtkErrorMacro(<< "Please specify FileName to use");
    return;
  }

  // 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 X3D file.");
    return;
  }

  // try opening the files
  if (this->Binary)
  {
    vtkX3DExporterFIWriter* temp = vtkX3DExporterFIWriter::New();
    temp->SetFastest(this->GetFastest());
    writer.TakeReference(temp);
  }
  else
  {
    writer = vtkSmartPointer<vtkX3DExporterXMLWriter>::New();
  }

  if (this->WriteToOutputString)
  {
    if (!writer->OpenStream())
    {
      vtkErrorMacro(<< "unable to open X3D stream");
      return;
    }
  }
  else
  {
    if (!writer->OpenFile(this->FileName))
    {
      vtkErrorMacro(<< "unable to open X3D file " << this->FileName);
      return;
    }
  }

  //
  //  Write header
  //
  vtkDebugMacro("Writing X3D file");

  writer->StartDocument();

  writer->StartNode(X3D);
  writer->SetField(profile, "Immersive");
  writer->SetField(vtkX3D::version, "3.0");

  writer->StartNode(head);

  writer->StartNode(meta);
  writer->SetField(name, "filename");
  writer->SetField(content, this->FileName ? this->FileName : "Stream");
  writer->EndNode();

  writer->StartNode(meta);
  writer->SetField(name, "generator");
  writer->SetField(content, "Visualization ToolKit X3D exporter v0.9.1");
  writer->EndNode();

  writer->StartNode(meta);
  writer->SetField(name, "numberofelements");
  std::ostringstream ss;
  ss << ren->GetActors()->GetNumberOfItems();
  writer->SetField(content, ss.str().c_str());
  writer->EndNode();

  writer->EndNode(); // head

  writer->StartNode(Scene);

  // Start write the Background
  writer->StartNode(Background);
  writer->SetField(skyColor, SFVEC3F, ren->GetBackground());
  writer->EndNode();
  // End of Background

  // Start write the Camera
  cam = ren->GetActiveCamera();
  writer->StartNode(Viewpoint);
  writer->SetField(
    fieldOfView, static_cast<float>(vtkMath::RadiansFromDegrees(cam->GetViewAngle())));
  writer->SetField(position, SFVEC3F, cam->GetPosition());
  writer->SetField(description, "Default View");
  writer->SetField(orientation, SFROTATION, cam->GetOrientationWXYZ());
  writer->SetField(centerOfRotation, SFVEC3F, cam->GetFocalPoint());
  writer->EndNode();
  // End of Camera

  // do the lights first the ambient then the others
  writer->StartNode(NavigationInfo);
  writer->SetField(type, "\"EXAMINE\" \"FLY\" \"ANY\"", true);
  writer->SetField(speed, static_cast<float>(this->Speed));
  writer->SetField(headlight, this->HasHeadLight(ren) != 0);
  writer->EndNode();

  writer->StartNode(DirectionalLight);
  writer->SetField(ambientIntensity, 1.0f);
  writer->SetField(intensity, 0.0f);
  writer->SetField(color, SFCOLOR, ren->GetAmbient());
  writer->EndNode();

  // label ROOT
  static double n[] = { 0.0, 0.0, 0.0 };
  writer->StartNode(Transform);
  writer->SetField(DEF, "ROOT");
  writer->SetField(translation, SFVEC3F, n);

  // make sure we have a default light
  // if we don't then use a headlight
  lc = ren->GetLights();
  vtkCollectionSimpleIterator lsit;
  for (lc->InitTraversal(lsit); (aLight = lc->GetNextLight(lsit));)
  {
    if (!aLight->LightTypeIsHeadlight())
    {
      this->WriteALight(aLight, writer);
    }
  }

  // do the actors now
  ac = ren->GetActors();
  vtkAssemblyPath* apath;
  vtkCollectionSimpleIterator ait;
  int index = 0;
  for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait));)
  {
    for (anActor->InitPathTraversal(); (apath = anActor->GetNextPath());)
    {
      if (anActor->GetVisibility() != 0)
      {
        aPart = static_cast<vtkActor*>(apath->GetLastNode()->GetViewProp());
        this->WriteAnActor(aPart, writer, index);
        index++;
      }
    }
  }
  writer->EndNode(); // ROOT Transform

  //////////////////////////////////////////////
  // do the 2D actors now
  a2Dc = ren->GetActors2D();

  if (a2Dc->GetNumberOfItems() != 0)
  {
    static double s[] = { 1000000.0, 1000000.0, 1000000.0 };
    writer->StartNode(ProximitySensor);
    writer->SetField(DEF, "PROX_LABEL");
    writer->SetField(size, SFVEC3F, s);
    writer->EndNode();

    // disable collision for the text annotations
    writer->StartNode(Collision);
    writer->SetField(enabled, false);

    // add a Label TRANS_LABEL for the text annotations and the sensor
    writer->StartNode(Transform);
    writer->SetField(DEF, "TRANS_LABEL");

    vtkAssemblyPath* apath2D;
    vtkCollectionSimpleIterator ait2D;
    for (a2Dc->InitTraversal(ait2D); (anTextActor2D = a2Dc->GetNextActor2D(ait2D));)
    {

      for (anTextActor2D->InitPathTraversal(); (apath2D = anTextActor2D->GetNextPath());)
      {
        aPart2D = static_cast<vtkActor2D*>(apath2D->GetLastNode()->GetViewProp());
        this->WriteATextActor2D(aPart2D, writer);
      }
    }
    writer->EndNode(); // Transform
    writer->EndNode(); // Collision

    writer->StartNode(ROUTE);
    writer->SetField(fromNode, "PROX_LABEL");
    writer->SetField(fromField, "position_changed");
    writer->SetField(toNode, "TRANS_LABEL");
    writer->SetField(toField, "set_translation");
    writer->EndNode(); // Route

    writer->StartNode(ROUTE);
    writer->SetField(fromNode, "PROX_LABEL");
    writer->SetField(fromField, "orientation_changed");
    writer->SetField(toNode, "TRANS_LABEL");
    writer->SetField(toField, "set_rotation");
    writer->EndNode(); // Route
  }
  /////////////////////////////////////////////////

  this->WriteAdditionalNodes(writer);

  writer->EndNode(); // Scene
  writer->EndNode(); // X3D
  writer->Flush();
  writer->EndDocument();
  writer->CloseFile();

  if (this->WriteToOutputString)
  {
    this->OutputStringLength = writer->GetOutputStringLength();
    this->OutputString = writer->RegisterAndGetOutputString();
  }
}

//------------------------------------------------------------------------------
void vtkX3DExporter::WriteALight(vtkLight* aLight, vtkX3DExporterWriter* writer)
{
  double *pos, *focus, *colord;
  double dir[3];

  pos = aLight->GetPosition();
  focus = aLight->GetFocalPoint();
  colord = aLight->GetDiffuseColor();

  dir[0] = focus[0] - pos[0];
  dir[1] = focus[1] - pos[1];
  dir[2] = focus[2] - pos[2];
  vtkMath::Normalize(dir);

  if (aLight->GetPositional())
  {
    if (aLight->GetConeAngle() >= 90.0)
    {
      writer->StartNode(PointLight);
    }
    else
    {
      writer->StartNode(SpotLight);
      writer->SetField(direction, SFVEC3F, dir);
      writer->SetField(cutOffAngle, static_cast<float>(aLight->GetConeAngle()));
    }
    writer->SetField(location, SFVEC3F, pos);
    writer->SetField(attenuation, SFVEC3F, aLight->GetAttenuationValues());
  }
  else
  {
    writer->StartNode(DirectionalLight);
    writer->SetField(direction, SFVEC3F, dir);
  }

  // TODO: Check correct color
  writer->SetField(color, SFCOLOR, colord);
  writer->SetField(intensity, static_cast<float>(aLight->GetIntensity()));
  writer->SetField(on, aLight->GetSwitch() != 0);
  writer->EndNode();
  writer->Flush();
}

//------------------------------------------------------------------------------
void vtkX3DExporter::WriteAnActor(vtkActor* anActor, vtkX3DExporterWriter* writer, int index)
{
  // see if the actor has a mapper. it could be an assembly
  vtkMapper* mapper = anActor->GetMapper();
  if (mapper == nullptr)
  {
    return;
  }
  mapper->Update();

  // validate mapper input dataset.
  vtkDataObject* dObj = mapper->GetInputDataObject(0, 0);
  vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(dObj);
  vtkPolyData* pd = vtkPolyData::SafeDownCast(dObj);
  if (cd == nullptr && pd == nullptr)
  {
    // we don't support the input dataset or is empty.
    return;
  }

  // first stuff out the transform
  vtkSmartPointer<vtkTransform> trans = vtkSmartPointer<vtkTransform>::New();
  trans->SetMatrix(anActor->vtkProp3D::GetMatrix());

  writer->StartNode(Transform);
  writer->SetField(translation, SFVEC3F, trans->GetPosition());
  writer->SetField(rotation, SFROTATION, trans->GetOrientationWXYZ());
  writer->SetField(scale, SFVEC3F, trans->GetScale());

  if (cd)
  {
    vtkSmartPointer<vtkCompositeDataIterator> iter;
    iter.TakeReference(cd->NewIterator());
    for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
    {
      if (vtkPolyData* currentPD = vtkPolyData::SafeDownCast(iter->GetCurrentDataObject()))
      {
        writer->StartNode(Group);
        if (iter->HasCurrentMetaData() &&
          iter->GetCurrentMetaData()->Has(vtkCompositeDataSet::NAME()))
        {
          if (const char* aname = iter->GetCurrentMetaData()->Get(vtkCompositeDataSet::NAME()))
          {
            std::string mfname = "\"" + std::string(aname) + "\"";
            writer->StartNode(MetadataString);
            writer->SetField(name, "name", false);
            writer->SetField(value, mfname.c_str(), true);
            writer->EndNode();
          }
        }
        this->WriteAPiece(currentPD, anActor, writer, index);
        writer->EndNode(); // close the Group.
      }
    }
  }
  else
  {
    assert(pd != nullptr);
    this->WriteAPiece(pd, anActor, writer, index);
  }
  writer->EndNode();
}

//------------------------------------------------------------------------------
void vtkX3DExporter::WriteAPiece(
  vtkPolyData* pd, vtkActor* anActor, vtkX3DExporterWriter* writer, int index)
{
  vtkPointData* pntData;
  vtkCellData* cellData;
  vtkPoints* points;
  vtkDataArray* normals = nullptr;
  vtkDataArray* tcoords = nullptr;
  vtkProperty* prop;
  vtkUnsignedCharArray* colors;
  vtkSmartPointer<vtkTransform> trans;

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

  if (pd == nullptr)
  {
    return;
  }

  // Create a temporary poly-data mapper that we use.
  vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();

  mapper->SetInputData(pd);
  mapper->SetScalarRange(anActor->GetMapper()->GetScalarRange());
  mapper->SetScalarVisibility(anActor->GetMapper()->GetScalarVisibility());
  mapper->SetLookupTable(anActor->GetMapper()->GetLookupTable());
  mapper->SetScalarMode(anActor->GetMapper()->GetScalarMode());

  // Essential to turn of interpolate scalars otherwise GetScalars() may return
  // nullptr. We restore value before returning.
  mapper->SetInterpolateScalarsBeforeMapping(0);
  if (mapper->GetScalarMode() == VTK_SCALAR_MODE_USE_POINT_FIELD_DATA ||
    mapper->GetScalarMode() == VTK_SCALAR_MODE_USE_CELL_FIELD_DATA)
  {
    if (anActor->GetMapper()->GetArrayAccessMode() == VTK_GET_ARRAY_BY_ID)
    {
      mapper->ColorByArrayComponent(
        anActor->GetMapper()->GetArrayId(), anActor->GetMapper()->GetArrayComponent());
    }
    else
    {
      mapper->ColorByArrayComponent(
        anActor->GetMapper()->GetArrayName(), anActor->GetMapper()->GetArrayComponent());
    }
  }

  prop = anActor->GetProperty();
  points = pd->GetPoints();
  pntData = pd->GetPointData();
  tcoords = pntData->GetTCoords();
  cellData = pd->GetCellData();

  colors = mapper->MapScalars(255.0);

  // Are we using cell colors? Pass the temporary mapper we created here since
  // we're assured that that mapper only has vtkPolyData as input and hence
  // don't run into issue when dealing with composite datasets.
  bool cell_colors = vtkX3DExporterWriterUsingCellColors(mapper);

  normals = pntData->GetNormals();

  // Are we using cell normals.
  bool cell_normals = false;
  if (prop->GetInterpolation() == VTK_FLAT || !normals)
  {
    // use cell normals, if any.
    normals = cellData->GetNormals();
    cell_normals = true;
  }

  // if we don't have colors and we have only lines & points
  // use emissive to color them
  bool writeEmissiveColor =
    !(normals || colors || pd->GetNumberOfPolys() || pd->GetNumberOfStrips());

  // write out the material properties to the mat file
  int representation = prop->GetRepresentation();

  if (representation == VTK_POINTS)
  {
    // If representation is points, then we don't have to render different cell
    // types in separate shapes, since the cells type no longer matter.
    {
      writer->StartNode(Shape);
      this->WriteAnAppearance(anActor, writeEmissiveColor, writer);
      vtkX3DExporterWriterRenderPoints(pd, colors, cell_colors, writer);
      writer->EndNode();
    }
  }
  else
  {
    // When rendering as lines or surface, we need to respect the cell
    // structure. This requires rendering polys, tstrips, lines, verts in
    // separate shapes.
    vtkCellArray* verts = pd->GetVerts();
    vtkCellArray* lines = pd->GetLines();
    vtkCellArray* polys = pd->GetPolys();
    vtkCellArray* tstrips = pd->GetStrips();

    vtkIdType numVerts = verts->GetNumberOfCells();
    vtkIdType numLines = lines->GetNumberOfCells();
    vtkIdType numPolys = polys->GetNumberOfCells();
    vtkIdType numStrips = tstrips->GetNumberOfCells();

    bool common_data_written = false;
    if (numPolys > 0)
    {
      writer->StartNode(Shape);
      // Write Appearance
      this->WriteAnAppearance(anActor, writeEmissiveColor, writer);
      // Write Geometry
      vtkX3DExporterWriterRenderFaceSet(VTK_POLYGON, representation, points, (numVerts + numLines),
        polys, colors, cell_colors, normals, cell_normals, tcoords, common_data_written, index,
        writer);
      writer->EndNode(); // close the  Shape
      common_data_written = true;
    }

    if (numStrips > 0)
    {
      writer->StartNode(Shape);
      // Write Appearance
      this->WriteAnAppearance(anActor, writeEmissiveColor, writer);
      // Write Geometry
      vtkX3DExporterWriterRenderFaceSet(VTK_TRIANGLE_STRIP, representation, points,
        (numVerts + numLines + numPolys), tstrips, colors, cell_colors, normals, cell_normals,
        tcoords, common_data_written, index, writer);
      writer->EndNode(); // close the  Shape
      common_data_written = true;
    }

    if (numLines > 0)
    {
      writer->StartNode(Shape);
      // Write Appearance
      this->WriteAnAppearance(anActor, writeEmissiveColor, writer);
      // Write Geometry
      vtkX3DExporterWriterRenderFaceSet(VTK_POLY_LINE,
        (representation == VTK_SURFACE ? VTK_WIREFRAME : representation), points, (numVerts), lines,
        colors, cell_colors, normals, cell_normals, tcoords, common_data_written, index, writer);
      writer->EndNode(); // close the  Shape
      common_data_written = true;
    }

    if (numVerts > 0)
    {
      writer->StartNode(Shape);
      this->WriteAnAppearance(anActor, writeEmissiveColor, writer);
      vtkX3DExporterWriterRenderVerts(points, verts, colors, cell_normals, writer);
      writer->EndNode(); // close the  Shape
    }
  }
}

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

  if (this->FileName)
  {
    os << indent << "FileName: " << this->FileName << "\n";
  }
  else
  {
    os << indent << "FileName: (null)\n";
  }
  os << indent << "Speed: " << this->Speed << "\n";
  os << indent << "Binary: " << this->Binary << "\n";
  os << indent << "Fastest: " << this->Fastest << endl;

  os << indent << "WriteToOutputString: " << (this->WriteToOutputString ? "On" : "Off")
     << std::endl;
  os << indent << "OutputStringLength: " << this->OutputStringLength << std::endl;
  if (this->OutputString)
  {
    os << indent << "OutputString: " << this->OutputString << std::endl;
  }
}

//------------------------------------------------------------------------------
void vtkX3DExporter::WriteATextActor2D(vtkActor2D* anTextActor2D, vtkX3DExporterWriter* writer)
{
  char* ds;
  vtkTextActor* ta;
  vtkTextProperty* tp;

  if (!anTextActor2D->IsA("vtkTextActor"))
  {
    return;
  }

  ta = static_cast<vtkTextActor*>(anTextActor2D);
  tp = ta->GetTextProperty();
  ds = ta->GetInput();

  if (ds == nullptr)
  {
    return;
  }

  double temp[3];

  writer->StartNode(Transform);
  temp[0] = ((ta->GetPosition()[0]) / (this->RenderWindow->GetSize()[0])) - 0.5;
  temp[1] = ((ta->GetPosition()[1]) / (this->RenderWindow->GetSize()[1])) - 0.5;
  temp[2] = -2.0;
  writer->SetField(translation, SFVEC3F, temp);
  temp[0] = temp[1] = temp[2] = 0.002;
  writer->SetField(scale, SFVEC3F, temp);

  writer->StartNode(Shape);

  writer->StartNode(Appearance);

  writer->StartNode(Material);
  temp[0] = 0.0;
  temp[1] = 0.0;
  temp[2] = 1.0;
  writer->SetField(diffuseColor, SFCOLOR, temp);
  tp->GetColor(temp);
  writer->SetField(emissiveColor, SFCOLOR, temp);
  writer->EndNode(); // Material

  writer->EndNode(); // Appearance

  writer->StartNode(Text);
  writer->SetField(vtkX3D::string, ds);

  std::string familyStr;
  switch (tp->GetFontFamily())
  {
    case 0:
    default:
      familyStr = "\"SANS\"";
      break;
    case 1:
      familyStr = "\"TYPEWRITER\"";
      break;
    case 2:
      familyStr = "\"SERIF\"";
      break;
  }

  std::string justifyStr;
  switch (tp->GetJustification())
  {
    case 0:
    default:
      justifyStr += "\"BEGIN\"";
      break;
    case 2:
      justifyStr += "\"END\"";
      break;
  }

  justifyStr += " \"BEGIN\"";

  writer->StartNode(FontStyle);
  writer->SetField(family, familyStr.c_str(), true);
  writer->SetField(topToBottom, tp->GetVerticalJustification() == 2);
  writer->SetField(justify, justifyStr.c_str(), true);
  writer->SetField(size, tp->GetFontSize());
  writer->EndNode(); // FontStyle
  writer->EndNode(); // Text
  writer->EndNode(); // Shape
  writer->EndNode(); // Transform
}

void vtkX3DExporter::WriteAnAppearance(
  vtkActor* anActor, bool emissive, vtkX3DExporterWriter* writer)
{
  double tempd[3];
  double tempf2;

  vtkProperty* prop = anActor->GetProperty();

  writer->StartNode(Appearance);
  writer->StartNode(Material);
  writer->SetField(ambientIntensity, static_cast<float>(prop->GetAmbient()));

  if (emissive)
  {
    tempf2 = prop->GetAmbient();
    prop->GetAmbientColor(tempd);
    tempd[0] *= tempf2;
    tempd[1] *= tempf2;
    tempd[2] *= tempf2;
  }
  else
  {
    tempd[0] = tempd[1] = tempd[2] = 0.0f;
  }
  writer->SetField(emissiveColor, SFCOLOR, tempd);

  // Set diffuse color
  tempf2 = prop->GetDiffuse();
  prop->GetDiffuseColor(tempd);
  tempd[0] *= tempf2;
  tempd[1] *= tempf2;
  tempd[2] *= tempf2;
  writer->SetField(diffuseColor, SFCOLOR, tempd);

  // Set specular color
  tempf2 = prop->GetSpecular();
  prop->GetSpecularColor(tempd);
  tempd[0] *= tempf2;
  tempd[1] *= tempf2;
  tempd[2] *= tempf2;
  writer->SetField(specularColor, SFCOLOR, tempd);

  // Material shininess
  writer->SetField(shininess, static_cast<float>(prop->GetSpecularPower() / 128.0));
  // Material transparency
  writer->SetField(transparency, static_cast<float>(1.0 - prop->GetOpacity()));
  writer->EndNode(); // close material

  // is there a texture map
  if (anActor->GetTexture())
  {
    this->WriteATexture(anActor, writer);
  }
  writer->EndNode(); // close appearance
}

void vtkX3DExporter::WriteATexture(vtkActor* anActor, vtkX3DExporterWriter* writer)
{
  vtkTexture* aTexture = anActor->GetTexture();
  int *size, xsize, ysize;
  vtkDataArray* scalars;
  vtkDataArray* mappedScalars;
  unsigned char* txtrData;
  int totalValues;

  // make sure it is updated and then 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;
      }
    }
  }

  std::vector<int> imageDataVec;
  imageDataVec.push_back(xsize);
  imageDataVec.push_back(ysize);
  imageDataVec.push_back(mappedScalars->GetNumberOfComponents());

  totalValues = xsize * ysize;
  txtrData = static_cast<vtkUnsignedCharArray*>(mappedScalars)->GetPointer(0);
  for (int i = 0; i < totalValues; i++)
  {
    int result = 0;
    for (int j = 0; j < imageDataVec[2]; j++)
    {
      result = result << 8;
      result += *txtrData;
      txtrData++;
    }
    imageDataVec.push_back(result);
  }

  writer->StartNode(PixelTexture);
  writer->SetField(image, &(imageDataVec.front()), imageDataVec.size(), true);
  if (!(aTexture->GetRepeat()))
  {
    writer->SetField(repeatS, false);
    writer->SetField(repeatT, false);
  }
  writer->EndNode();
}

//------------------------------------------------------------------------------
vtkTypeBool vtkX3DExporter::HasHeadLight(vtkRenderer* ren)
{
  // make sure we have a default light
  // if we don't then use a headlight
  vtkLightCollection* lc = ren->GetLights();
  vtkCollectionSimpleIterator lsit;
  vtkLight* aLight = nullptr;
  for (lc->InitTraversal(lsit); (aLight = lc->GetNextLight(lsit));)
  {
    if (aLight->LightTypeIsHeadlight())
    {
      return 1;
    }
  }
  return 0;
}

// Determine if we're using cell data for scalar coloring. Returns true if
// that's the case.
static bool vtkX3DExporterWriterUsingCellColors(vtkMapper* mapper)
{
  int cellFlag = 0;
  vtkAbstractMapper::GetScalars(mapper->GetInput(), mapper->GetScalarMode(),
    mapper->GetArrayAccessMode(), mapper->GetArrayId(), mapper->GetArrayName(), cellFlag);
  return (cellFlag == 1);
}

//------------------------------------------------------------------------------
static bool vtkX3DExporterWriterRenderFaceSet(int cellType, int representation, vtkPoints* points,
  vtkIdType cellOffset, vtkCellArray* cells, vtkUnsignedCharArray* colors, bool cell_colors,
  vtkDataArray* normals, bool cell_normals, vtkDataArray* tcoords, bool common_data_written,
  int index, vtkX3DExporterWriter* writer)
{
  std::vector<int> coordIndexVector;
  std::vector<int> cellIndexVector;

  vtkIdType npts = 0;
  const vtkIdType* indx = nullptr;

  if (cellType == VTK_POLYGON || cellType == VTK_POLY_LINE)
  {
    for (cells->InitTraversal(); cells->GetNextCell(npts, indx); cellOffset++)
    {
      for (vtkIdType cc = 0; cc < npts; cc++)
      {
        coordIndexVector.push_back(static_cast<int>(indx[cc]));
      }

      if (representation == VTK_WIREFRAME && npts > 2 && cellType == VTK_POLYGON)
      {
        // close the polygon.
        coordIndexVector.push_back(static_cast<int>(indx[0]));
      }
      coordIndexVector.push_back(-1);

      vtkIdType cellid = cellOffset;
      cellIndexVector.push_back(cellid);
    }
  }
  else // cellType == VTK_TRIANGLE_STRIP
  {
    for (cells->InitTraversal(); cells->GetNextCell(npts, indx); cellOffset++)
    {
      for (vtkIdType cc = 2; cc < npts; cc++)
      {
        vtkIdType i1;
        vtkIdType i2;
        if (cc % 2)
        {
          i1 = cc - 1;
          i2 = cc - 2;
        }
        else
        {
          i1 = cc - 2;
          i2 = cc - 1;
        }
        coordIndexVector.push_back(static_cast<int>(indx[i1]));
        coordIndexVector.push_back(static_cast<int>(indx[i2]));
        coordIndexVector.push_back(static_cast<int>(indx[cc]));

        if (representation == VTK_WIREFRAME)
        {
          // close the polygon when drawing lines
          coordIndexVector.push_back(static_cast<int>(indx[i1]));
        }
        coordIndexVector.push_back(-1);

        vtkIdType cellid = cellOffset;
        cellIndexVector.push_back(static_cast<int>(cellid));
      }
    }
  }

  if (representation == VTK_SURFACE)
  {
    writer->StartNode(IndexedFaceSet);
    writer->SetField(solid, false);
    writer->SetField(colorPerVertex, !cell_colors);
    writer->SetField(normalPerVertex, !cell_normals);
    writer->SetField(coordIndex, &(coordIndexVector.front()), coordIndexVector.size());
  }
  else
  {
    // don't save normals/tcoords when saving wireframes.
    normals = nullptr;
    tcoords = nullptr;

    writer->StartNode(IndexedLineSet);
    writer->SetField(colorPerVertex, !cell_colors);
    writer->SetField(coordIndex, &(coordIndexVector.front()), coordIndexVector.size());
  }

  if (normals && cell_normals && representation == VTK_SURFACE)
  {
    writer->SetField(normalIndex, &(cellIndexVector.front()), cellIndexVector.size());
  }

  if (colors && cell_colors)
  {
    writer->SetField(colorIndex, &(cellIndexVector.front()), cellIndexVector.size());
  }

  // Now save Coordinate, Color, Normal TextureCoordinate nodes.
  // Use DEF/USE to avoid duplicates.
  if (!common_data_written)
  {
    vtkX3DExporterWriteData(points, normals, tcoords, colors, index, writer);
  }
  else
  {
    vtkX3DExporterUseData(
      (normals != nullptr), (tcoords != nullptr), (colors != nullptr), index, writer);
  }

  writer->EndNode(); // end IndexedFaceSet or IndexedLineSet
  return true;
}

static void vtkX3DExporterWriteData(vtkPoints* points, vtkDataArray* normals, vtkDataArray* tcoords,
  vtkUnsignedCharArray* colors, int index, vtkX3DExporterWriter* writer)
{
  char indexString[100];
  snprintf(indexString, sizeof(indexString), "%04d", index);

  // write out the points
  std::string defString = "VTKcoordinates";
  writer->StartNode(Coordinate);
  writer->SetField(DEF, defString.append(indexString).c_str());
  writer->SetField(point, MFVEC3F, points->GetData());
  writer->EndNode();

  // write out the point data
  if (normals)
  {
    defString = "VTKnormals";
    writer->StartNode(Normal);
    writer->SetField(DEF, defString.append(indexString).c_str());
    writer->SetField(vtkX3D::vector, MFVEC3F, normals);
    writer->EndNode();
  }

  // write out the point data
  if (tcoords)
  {
    defString = "VTKtcoords";
    writer->StartNode(TextureCoordinate);
    writer->SetField(DEF, defString.append(indexString).c_str());
    writer->SetField(point, MFVEC2F, tcoords);
    writer->EndNode();
  }

  // write out the point data
  if (colors)
  {
    defString = "VTKcolors";
    writer->StartNode(Color);
    writer->SetField(DEF, defString.append(indexString).c_str());

    std::vector<double> colorVec;
    unsigned char c[4];
    for (int i = 0; i < colors->GetNumberOfTuples(); i++)
    {
      colors->GetTypedTuple(i, c);
      colorVec.push_back(c[0] / 255.0);
      colorVec.push_back(c[1] / 255.0);
      colorVec.push_back(c[2] / 255.0);
    }
    writer->SetField(color, &(colorVec.front()), colorVec.size());
    writer->EndNode();
  }
}

static void vtkX3DExporterUseData(
  bool normals, bool tcoords, bool colors, int index, vtkX3DExporterWriter* writer)
{
  char indexString[100];
  snprintf(indexString, sizeof(indexString), "%04d", index);
  std::string defString = "VTKcoordinates";
  writer->StartNode(Coordinate);
  writer->SetField(USE, defString.append(indexString).c_str());
  writer->EndNode();

  // write out the point data
  if (normals)
  {
    defString = "VTKnormals";
    writer->StartNode(Normal);
    writer->SetField(USE, defString.append(indexString).c_str());
    writer->EndNode();
  }

  // write out the point data
  if (tcoords)
  {
    defString = "VTKtcoords";
    writer->StartNode(TextureCoordinate);
    writer->SetField(USE, defString.append(indexString).c_str());
    writer->EndNode();
  }

  // write out the point data
  if (colors)
  {
    defString = "VTKcolors";
    writer->StartNode(Color);
    writer->SetField(USE, defString.append(indexString).c_str());
    writer->EndNode();
  }
}

static bool vtkX3DExporterWriterRenderVerts(vtkPoints* points, vtkCellArray* cells,
  vtkUnsignedCharArray* colors, bool cell_colors, vtkX3DExporterWriter* writer)
{
  std::vector<double> colorVector;

  if (colors)
  {
    vtkIdType cellId = 0;
    vtkIdType npts = 0;
    const vtkIdType* indx = nullptr;
    for (cells->InitTraversal(); cells->GetNextCell(npts, indx); cellId++)
    {
      for (vtkIdType cc = 0; cc < npts; cc++)
      {
        unsigned char color[4];
        if (cell_colors)
        {
          colors->GetTypedTuple(cellId, color);
        }
        else
        {
          colors->GetTypedTuple(indx[cc], color);
        }

        colorVector.push_back(color[0] / 255.0);
        colorVector.push_back(color[1] / 255.0);
        colorVector.push_back(color[2] / 255.0);
      }
    }
  }

  writer->StartNode(PointSet);
  writer->StartNode(Coordinate);
  writer->SetField(point, MFVEC3F, points->GetData());
  writer->EndNode();
  if (colors)
  {
    writer->StartNode(Color);
    writer->SetField(point, &(colorVector.front()), colorVector.size());
    writer->EndNode();
  }
  return true;
}

static bool vtkX3DExporterWriterRenderPoints(
  vtkPolyData* pd, vtkUnsignedCharArray* colors, bool cell_colors, vtkX3DExporterWriter* writer)
{
  if (pd->GetNumberOfCells() == 0)
  {
    return false;
  }

  std::vector<double> colorVec;
  std::vector<double> coordinateVec;

  vtkPoints* points = pd->GetPoints();

  // We render as cells so that even when coloring with cell data, the points
  // are assigned colors correctly.

  if ((colors != nullptr) && cell_colors)
  {
    // Cell colors are used, however PointSet element can only have point
    // colors, hence we use this method. Although here we end up with duplicate
    // points, that's exactly what happens in case of OpenGL rendering, so it's
    // okay.
    vtkIdType numCells = pd->GetNumberOfCells();
    vtkSmartPointer<vtkIdList> pointIds = vtkSmartPointer<vtkIdList>::New();
    for (vtkIdType cid = 0; cid < numCells; cid++)
    {
      pointIds->Reset();
      pd->GetCellPoints(cid, pointIds);

      // Get the color for this cell.
      unsigned char color[4];
      colors->GetTypedTuple(cid, color);
      double dcolor[3];
      dcolor[0] = color[0] / 255.0;
      dcolor[1] = color[1] / 255.0;
      dcolor[2] = color[2] / 255.0;

      for (vtkIdType cc = 0; cc < pointIds->GetNumberOfIds(); cc++)
      {
        vtkIdType pid = pointIds->GetId(cc);
        double* point = points->GetPoint(pid);
        coordinateVec.push_back(point[0]);
        coordinateVec.push_back(point[1]);
        coordinateVec.push_back(point[2]);
        colorVec.push_back(dcolor[0]);
        colorVec.push_back(dcolor[1]);
        colorVec.push_back(dcolor[2]);
      }
    }
  }
  else
  {
    // Colors are point colors, simply render all the points and corresponding
    // colors.
    vtkIdType numPoints = points->GetNumberOfPoints();
    for (vtkIdType pid = 0; pid < numPoints; pid++)
    {
      double* point = points->GetPoint(pid);
      coordinateVec.push_back(point[0]);
      coordinateVec.push_back(point[1]);
      coordinateVec.push_back(point[2]);

      if (colors)
      {
        unsigned char color[4];
        colors->GetTypedTuple(pid, color);
        colorVec.push_back(color[0] / 255.0);
        colorVec.push_back(color[1] / 255.0);
        colorVec.push_back(color[2] / 255.0);
      }
    }
  }

  writer->StartNode(PointSet);
  writer->StartNode(Coordinate);
  writer->SetField(point, &(coordinateVec.front()), coordinateVec.size());
  writer->EndNode(); // Coordinate
  if (colors)
  {
    writer->StartNode(Color);
    writer->SetField(color, &(colorVec.front()), colorVec.size());
    writer->EndNode(); // Color
  }
  writer->EndNode(); // PointSet
  return true;
}
//------------------------------------------------------------------------------
char* vtkX3DExporter::RegisterAndGetOutputString()
{
  char* tmp = this->OutputString;

  this->OutputString = nullptr;
  this->OutputStringLength = 0;

  return tmp;
}
VTK_ABI_NAMESPACE_END
