/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtk3DSImporter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtk3DSImporter.h"

#include "vtkActor.h"
#include "vtkByteSwap.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkLight.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkPolyDataNormals.h"
#include "vtkProperty.h"
#include "vtkRenderer.h"
#include "vtkStripper.h"

vtkStandardNewMacro(vtk3DSImporter);

// Silence warning like
// "dereferencing type-punned pointer will break strict-aliasing rules"
// This file just has too many of them.
// This is due to the use of (vtk3DSList **)&root in VTK_LIST_* macros
// defined in vtk3DS.h
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif

// Wrap these globals in a namespace to prevent identifier collisions with
// inline code on MSVC2015:
namespace vtk3DS {
static vtk3DSColour black = {0.0f, 0.0f, 0.0f};
static char   objName[80] = "";
static vtk3DSColour fogColour  = {0.0f, 0.0f, 0.0f};
static vtk3DSColour col        = {0.0f, 0.0f, 0.0f};
static vtk3DSColour globalAmb  = {0.1f, 0.1f, 0.1f};
static vtk3DSVector pos        = {0.0, 0.0, 0.0};
static vtk3DSVector target     = {0.0, 0.0, 0.0};
static float  hotspot = -1;
static float  falloff = -1;

/* Default material property */
static vtk3DSMatProp defaultMaterial =
  { "Default", nullptr,
    {1.0, 1.0, 1.0}, {1.0, 1.0, 1.0}, {1.0, 1.0, 1.0},
    70.0, // shininess
    0.0,  // transparency
    0.0,  // reflection
    0,// self illumination
    "",   // tex_map
    0.0,  // tex_strength
    "",   // bump_map
    0.0,  // bump_strength
    nullptr};// vtkProperty

} // end namespace vtk3DS

static void cleanup_name (char *);
static void list_insert (vtk3DSList **root, vtk3DSList *new_node);
static void *list_find (vtk3DSList **root, const char *name);
static void list_kill (vtk3DSList **root);
static vtk3DSMatProp *create_mprop ();
static vtk3DSMesh *create_mesh (char *name, int vertices, int faces);
static int parse_3ds_file (vtk3DSImporter *importer);
static void parse_3ds (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_mdata (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_fog (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_fog_bgnd (vtk3DSImporter *importer);
static void parse_mat_entry (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static char *parse_mapname (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_named_object (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_n_tri_object (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_point_array (vtk3DSImporter *importer, vtk3DSMesh *mesh);
static void parse_face_array (vtk3DSImporter *importer, vtk3DSMesh *mesh, vtk3DSChunk *mainchunk);
static void parse_msh_mat_group (vtk3DSImporter *importer, vtk3DSMesh *mesh);
static void parse_smooth_group (vtk3DSImporter *importer);
static void parse_mesh_matrix (vtk3DSImporter *importer, vtk3DSMesh *mesh);
static void parse_n_direct_light (vtk3DSImporter *importer, vtk3DSChunk *mainchunk);
static void parse_dl_spotlight (vtk3DSImporter *importer);
static void parse_n_camera (vtk3DSImporter *importer);
static void parse_colour (vtk3DSImporter *importer, vtk3DSColour *colour);
static void parse_colour_f (vtk3DSImporter *importer, vtk3DSColour *colour);
static void parse_colour_24 (vtk3DSImporter *importer, vtk3DSColour_24 *colour);
static float parse_percentage (vtk3DSImporter *importer);
static short parse_int_percentage (vtk3DSImporter *importer);
static float parse_float_percentage (vtk3DSImporter *importer);
static vtk3DSMaterial *update_materials (vtk3DSImporter *importer, const char *new_material, int ext);
static void start_chunk (vtk3DSImporter *importer, vtk3DSChunk *chunk);
static void end_chunk (vtk3DSImporter *importer, vtk3DSChunk *chunk);
static byte read_byte (vtk3DSImporter *importer);
static word read_word (vtk3DSImporter *importer);
static word peek_word (vtk3DSImporter *importer);
static dword peek_dword (vtk3DSImporter *importer);
static float read_float (vtk3DSImporter *importer);
static void read_point (vtk3DSImporter *importer, vtk3DSVector v);
static char *read_string (vtk3DSImporter *importer);

vtk3DSImporter::vtk3DSImporter ()
{
  this->OmniList = nullptr;
  this->SpotLightList = nullptr;
  this->CameraList = nullptr;
  this->MeshList = nullptr;
  this->MaterialList = nullptr;
  this->MatPropList = nullptr;
  this->FileName = nullptr;
  this->FileFD = nullptr;
  this->ComputeNormals = 0;
}

int vtk3DSImporter::ImportBegin ()
{
  vtkDebugMacro(<< "Opening import file as binary");
  this->FileFD = fopen (this->FileName, "rb");
  if (this->FileFD == nullptr)
  {
    vtkErrorMacro(<< "Unable to open file: "<< this->FileName);
    return 0;
  }
  return this->Read3DS ();
}

void vtk3DSImporter::ImportEnd ()
{
  vtkDebugMacro(<<"Closing import file");
  if ( this->FileFD != nullptr )
  {
    fclose (this->FileFD);
  }
  this->FileFD = nullptr;
}

int vtk3DSImporter::Read3DS ()
{
  vtk3DSMatProp *aMaterial;

  if (parse_3ds_file (this) == 0)
  {
    vtkErrorMacro (<<  "Error readings .3ds file: " << this->FileName << "\n");
    return 0;
  }


  // create a vtk3DSMatProp and fill if in with default
  aMaterial = (vtk3DSMatProp *) malloc (sizeof (vtk3DSMatProp));
  *aMaterial = vtk3DS::defaultMaterial;
  aMaterial->aProperty = vtkProperty::New ();
  VTK_LIST_INSERT (this->MatPropList, aMaterial);
  return 1;
}

void vtk3DSImporter::ImportActors (vtkRenderer *renderer)
{
  vtk3DSMatProp *material;
  vtk3DSMesh *mesh;
  vtkStripper *polyStripper;
  vtkPolyDataNormals *polyNormals;
  vtkPolyDataMapper *polyMapper;
  vtkPolyData *polyData;
  vtkActor *actor;

  // walk the list of meshes, creating actors
  for (mesh = this->MeshList; mesh != (vtk3DSMesh *) nullptr;
       mesh = (vtk3DSMesh *) mesh->next)
  {
    if (mesh->faces == 0)
    {
      vtkWarningMacro (<< "part " << mesh->name << " has zero faces... skipping\n");
      continue;
    }

    polyData = this->GeneratePolyData (mesh);
    mesh->aMapper = polyMapper = vtkPolyDataMapper::New ();
    mesh->aStripper = polyStripper = vtkStripper::New ();

    // if ComputeNormals is on, insert a vtkPolyDataNormals filter
    if (this->ComputeNormals)
    {
      mesh->aNormals = polyNormals = vtkPolyDataNormals::New ();
      polyNormals->SetInputData (polyData);
      polyStripper->SetInputConnection(polyNormals->GetOutputPort());
    }
    else
    {
      polyStripper->SetInputData(polyData);
    }

    polyMapper->SetInputConnection(polyStripper->GetOutputPort());
    vtkDebugMacro (<< "Importing Actor: " << mesh->name);
    mesh->anActor = actor = vtkActor::New ();
    actor->SetMapper (polyMapper);
    material = (vtk3DSMatProp *)VTK_LIST_FIND(this->MatPropList, mesh->mtl[0]->name);
    actor->SetProperty (material->aProperty);
    renderer->AddActor (actor);
  }
}

vtkPolyData *vtk3DSImporter::GeneratePolyData (vtk3DSMesh *mesh)
{
  int i;
  vtk3DSFace  *face;
  vtkCellArray *triangles;
  vtkPoints *vertices;
  vtkPolyData *polyData;

  face = mesh->face;
  mesh->aCellArray = triangles = vtkCellArray::New ();
  triangles->Allocate(mesh->faces * 3);
  for (i = 0; i < mesh->faces; i++, face++)
  {
    triangles->InsertNextCell (3);
    triangles->InsertCellPoint (face->a);
    triangles->InsertCellPoint (face->b);
    triangles->InsertCellPoint (face->c);
  }

  mesh->aPoints = vertices = vtkPoints::New ();
  vertices->Allocate(mesh->vertices);
  for (i = 0; i < mesh->vertices; i++)
  {
    vertices->InsertPoint (i, (float *) mesh->vertex[i]);
  }
  mesh->aPolyData = polyData = vtkPolyData::New ();
  polyData->SetPolys (triangles);
  polyData->SetPoints (vertices);

  return polyData;
}

void vtk3DSImporter::ImportCameras (vtkRenderer *renderer)
{
  vtkCamera *aCamera;
  vtk3DSCamera *camera;

  // walk the list of cameras and create vtk cameras
  for (camera = this->CameraList; camera != (vtk3DSCamera *) nullptr; camera = (vtk3DSCamera *) camera->next)
  {
    camera->aCamera = aCamera = vtkCamera::New ();
    aCamera->SetPosition (camera->pos[0], camera->pos[1], camera->pos[2]);
    aCamera->SetFocalPoint (camera->target[0], camera->target[1], camera->target[2]);
    aCamera->SetViewUp (0, 0, 1);
    aCamera->SetClippingRange (.1,10000);
    aCamera->Roll (camera->bank);
    renderer->SetActiveCamera (aCamera);
    vtkDebugMacro (<< "Importing Camera: " << camera->name);
  }
}

void vtk3DSImporter::ImportLights (vtkRenderer *renderer)
{
  vtk3DSOmniLight *omniLight;
  vtk3DSSpotLight *spotLight;
  vtkLight *aLight;

  // just walk the list of omni lights, creating vtk lights
  for (omniLight = this->OmniList; omniLight != (vtk3DSOmniLight *) nullptr;
       omniLight = (vtk3DSOmniLight *) omniLight->next)
  {
  omniLight->aLight = aLight = vtkLight::New ();
  aLight->SetPosition (omniLight->pos[0],
                       omniLight->pos[1],
                       omniLight->pos[2]);
  aLight->SetFocalPoint (0, 0, 0);
  aLight->SetColor (omniLight->col.red,
                    omniLight->col.green,
                    omniLight->col.blue);
  renderer->AddLight (aLight);
  vtkDebugMacro (<< "Importing Omni Light: " << omniLight->name);
  }

  // now walk the list of spot lights, creating vtk lights
  for (spotLight = this->SpotLightList; spotLight != (vtk3DSSpotLight *) nullptr;
       spotLight = (vtk3DSSpotLight *) spotLight->next)
  {
  spotLight->aLight = aLight = vtkLight::New ();
  aLight->PositionalOn ();
  aLight->SetPosition (spotLight->pos[0],
                       spotLight->pos[1],
                       spotLight->pos[2]);
  aLight->SetFocalPoint (spotLight->target[0],
                         spotLight->target[1],
                         spotLight->target[2]);
  aLight->SetColor (spotLight->col.red,
                    spotLight->col.green,
                    spotLight->col.blue);
  aLight->SetConeAngle (spotLight->falloff);
  renderer->AddLight (aLight);
  vtkDebugMacro (<< "Importing Spot Light: " << spotLight->name);
  }
}

void vtk3DSImporter::ImportProperties (vtkRenderer *vtkNotUsed(renderer))
{
  float amb = 0.1, dif = 0.9;
  float dist_white, dist_diff, phong, phong_size;
  vtkProperty *property;
  vtk3DSMatProp *m;

  // just walk the list of material properties, creating vtk properties
  for (m = this->MatPropList; m != (vtk3DSMatProp *) nullptr; m = (vtk3DSMatProp *) m->next)
  {
    if (m->self_illum)
    {
      amb = 0.9;
      dif = 0.1;
    }

    dist_white = fabs(1.0 - m->specular.red) +
         fabs(1.0 - m->specular.green) +
         fabs(1.0 - m->specular.blue);

    dist_diff  = fabs(m->diffuse.red   - m->specular.red) +
         fabs(m->diffuse.green - m->specular.green) +
         fabs(m->diffuse.blue  - m->specular.blue);

    if (dist_diff < dist_white)
    {
      dif = .1; amb = .8;
    }

    phong_size = 0.7*m->shininess;
    if (phong_size < 1.0)
    {
      phong_size = 1.0;
    }
    if (phong_size > 30.0)
    {
      phong = 1.0;
    }
    else
    {
      phong = phong_size/30.0;
    }
  property = m->aProperty;
  property->SetAmbientColor(m->ambient.red, m->ambient.green, m->ambient.blue);
  property->SetAmbient (amb);
  property->SetDiffuseColor(m->diffuse.red, m->diffuse.green, m->diffuse.blue);
  property->SetDiffuse (dif);
  property->SetSpecularColor(m->specular.red, m->specular.green, m->specular.blue);
  property->SetSpecular (phong);
  property->SetSpecularPower(phong_size);
  property->SetOpacity(1.0 - m->transparency);
  vtkDebugMacro(<< "Importing Property: " << m->name);

  m->aProperty = property;
  }
}

/* Insert a new node into the list */
static void list_insert (vtk3DSList **root, vtk3DSList *new_node)
{
  new_node->next = *root;
  *root = new_node;
}


/* Find the node with the specified name */
static void *list_find (vtk3DSList **root, const char *name)
{
  vtk3DSList *p;
  for (p = *root; p != (vtk3DSList *) nullptr; p = (vtk3DSList *) p->next)
  {
    if (strcmp (p->name, name) == 0)
    {
      break;
    }
  }
  return (void *)p;
}

/* Delete the entire list */
static void list_kill (vtk3DSList **root)
{
  vtk3DSList *temp;

  while (*root != (vtk3DSList *) nullptr)
  {
    temp = *root;
    *root = (vtk3DSList *) (*root)->next;
    free (temp);
  }
}

/* Add a new material to the material list */
static vtk3DSMaterial *update_materials (vtk3DSImporter *importer, const char *new_material, int ext)
{
  vtk3DSMaterial *p;

  p = (vtk3DSMaterial *) VTK_LIST_FIND (importer->MaterialList, new_material);

  if (p == nullptr)
  {
    p = (vtk3DSMaterial *) malloc (sizeof (*p));
    strcpy (p->name, new_material);
    p->external = ext;
    VTK_LIST_INSERT (importer->MaterialList, p);
  }
  return p;
}


static vtk3DSMatProp *create_mprop()
{
  vtk3DSMatProp *new_mprop;

  new_mprop = (vtk3DSMatProp *) malloc (sizeof(*new_mprop));
  strcpy (new_mprop->name, "");
  new_mprop->ambient = vtk3DS::black;
  new_mprop->diffuse = vtk3DS::black;
  new_mprop->specular = vtk3DS::black;
  new_mprop->shininess = 0.0;
  new_mprop->transparency = 0.0;
  new_mprop->reflection = 0.0;
  new_mprop->self_illum = 0;

  strcpy (new_mprop->tex_map, "");
  new_mprop->tex_strength = 0.0;

  strcpy (new_mprop->bump_map, "");
  new_mprop->bump_strength = 0.0;

  new_mprop->aProperty = vtkProperty::New ();
  return new_mprop;
}


/* Create a new mesh */
static vtk3DSMesh *create_mesh (char *name, int vertices, int faces)
{
  vtk3DSMesh *new_mesh;

  new_mesh = (vtk3DSMesh *) malloc (sizeof(*new_mesh));
  strcpy (new_mesh->name, name);

  new_mesh->vertices = vertices;

  if (vertices <= 0)
  {
    new_mesh->vertex = nullptr;
  }
  else
  {
    new_mesh->vertex = (vtk3DSVector *) malloc(vertices * sizeof(*new_mesh->vertex));
  }

  new_mesh->faces = faces;

  if (faces <= 0)
  {
    new_mesh->face = nullptr;
    new_mesh->mtl = nullptr;
  }
  else
  {
    new_mesh->face = (vtk3DSFace *) malloc (faces * sizeof(*new_mesh->face));
    new_mesh->mtl = (vtk3DSMaterial **) malloc (faces * sizeof(*new_mesh->mtl));
  }

  new_mesh->hidden = 0;
  new_mesh->shadow = 1;

  new_mesh->anActor = nullptr;
  new_mesh->aMapper = nullptr;
  new_mesh->aNormals = nullptr;
  new_mesh->aStripper = nullptr;
  new_mesh->aPoints = nullptr;
  new_mesh->aCellArray = nullptr;
  new_mesh->aPolyData = nullptr;
  return new_mesh;
}


static int parse_3ds_file(vtk3DSImporter *importer)
{
  vtk3DSChunk chunk;

  start_chunk(importer, &chunk);

  if (chunk.tag == 0x4D4D)
  {
    parse_3ds (importer, &chunk);
  }
  else
  {
    vtkGenericWarningMacro(<< "Error: Input file is not .3DS format\n");
    return 0;
  }

  end_chunk (importer, &chunk);
  return 1;
}

static void parse_3ds (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x3D3D: parse_mdata (importer, &chunk);
          break;
      }
    }
    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);
}


static void parse_mdata (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;
  vtk3DSColour bgnd_colour;

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x2100: parse_colour (importer, &vtk3DS::globalAmb);
          break;
        case 0x1200: parse_colour (importer, &bgnd_colour);
          break;
        case 0x2200: parse_fog (importer, &chunk);
          break;
        case 0x2210: parse_fog_bgnd(importer);
          break;
        case 0xAFFF: parse_mat_entry (importer, &chunk);
          break;
        case 0x4000: parse_named_object (importer, &chunk);
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);
}


static void parse_fog (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;

  (void)read_float(importer);
  (void)read_float(importer);
  (void) read_float(importer);
  (void)read_float(importer);

  parse_colour (importer, &vtk3DS::fogColour);

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x2210: parse_fog_bgnd(importer);
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);
}


static void parse_fog_bgnd(vtk3DSImporter *vtkNotUsed(importer))
{
}


static void parse_mat_entry (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;
  vtk3DSMatProp *mprop;

  mprop = create_mprop();

  do
  {
    start_chunk (importer, &chunk);
    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0xA000: strcpy (mprop->name, read_string(importer));
          cleanup_name (mprop->name);
          break;

        case 0xA010: parse_colour (importer, &mprop->ambient);
          break;

        case 0xA020: parse_colour (importer, &mprop->diffuse);
          break;

        case 0xA030: parse_colour (importer, &mprop->specular);
          break;

        case 0xA040: mprop->shininess = 100.0*parse_percentage(importer);
          break;

        case 0xA050: mprop->transparency = parse_percentage(importer);
          break;

        case 0xA080: mprop->self_illum = 1;
          break;

        case 0xA220: mprop->reflection = parse_percentage(importer);
          (void)parse_mapname (importer, &chunk);
          break;

        case 0xA310: if (mprop->reflection == 0.0)
        {
          mprop->reflection = 1.0;
        }
        break;

        case 0xA200: mprop->tex_strength = parse_percentage(importer);
          strcpy (mprop->tex_map, parse_mapname (importer, &chunk));
          break;

        case 0xA230: mprop->bump_strength = parse_percentage(importer);
          strcpy (mprop->bump_map, parse_mapname (importer, &chunk));
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

    VTK_LIST_INSERT (importer->MatPropList, mprop);
}


static char *parse_mapname (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  static char name[80] = "";
  vtk3DSChunk chunk;

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0xA300: strcpy (name, read_string(importer));
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

    return name;
}


static void parse_named_object (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSMesh *mesh;
  vtk3DSChunk chunk;

  strcpy (vtk3DS::objName, read_string(importer));
  cleanup_name (vtk3DS::objName);

  mesh = nullptr;

  do
  {
    start_chunk (importer, &chunk);
    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x4100: parse_n_tri_object (importer, &chunk);
          break;
        case 0x4600: parse_n_direct_light (importer, &chunk);
          break;
        case 0x4700: parse_n_camera(importer);
          break;
        case 0x4010: if (mesh != nullptr)
        {
          mesh->hidden = 1;
        }
        break;
        case 0x4012: if (mesh != nullptr)
        {
          mesh->shadow = 0;
        }
        break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

}

static void parse_n_tri_object (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSMesh *mesh;
  vtk3DSChunk chunk;

  mesh = create_mesh (vtk3DS::objName, 0, 0);

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x4110: parse_point_array(importer, mesh);
          break;
        case 0x4120: parse_face_array (importer, mesh, &chunk);
          break;
        case 0x4160: parse_mesh_matrix(importer, mesh);
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

  VTK_LIST_INSERT (importer->MeshList, mesh);
}


static void parse_point_array(vtk3DSImporter *importer, vtk3DSMesh *mesh)
{
  int i;

  mesh->vertices = read_word(importer);
  mesh->vertex = (vtk3DSVector *) malloc (mesh->vertices * sizeof(*(mesh->vertex)));
  for (i = 0; i < mesh->vertices; i++)
  {
    read_point (importer, mesh->vertex[i]);
  }
}

static void parse_face_array (vtk3DSImporter *importer, vtk3DSMesh *mesh, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;
  int i;

  mesh->faces = read_word(importer);
  mesh->face = (vtk3DSFace *) malloc (mesh->faces * sizeof(*(mesh->face)));
  mesh->mtl = (vtk3DSMaterial **) malloc (mesh->faces * sizeof(*(mesh->mtl)));

  for (i = 0; i < mesh->faces; i++)
  {
    mesh->face[i].a = read_word(importer);
    mesh->face[i].b = read_word(importer);
    mesh->face[i].c = read_word(importer);
    (void)read_word(importer);

    mesh->mtl[i] = nullptr;
  }

  do
  {
    start_chunk (importer, &chunk);
    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x4130: parse_msh_mat_group(importer, mesh);
          break;
        case 0x4150: parse_smooth_group(importer);
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

  for (i = 0; i < mesh->faces; i++)
  {
    if (mesh->mtl[i] == (vtk3DSMaterial *) nullptr)
    {
      mesh->mtl[i] = update_materials (importer, "Default", 0);
    }
  }
}


static void parse_msh_mat_group(vtk3DSImporter *importer, vtk3DSMesh *mesh)
{
  vtk3DSMaterial *new_mtl;
  char mtlname[80];
  int  mtlcnt;
  int  i, face;

  strcpy (mtlname, read_string(importer));
  cleanup_name (mtlname);

  new_mtl = update_materials (importer, mtlname, 0);

  mtlcnt = read_word(importer);

  for (i = 0; i < mtlcnt; i++)
  {
    face = read_word(importer);
    mesh->mtl[face] = new_mtl;
  }
}

static void parse_smooth_group(vtk3DSImporter *vtkNotUsed(importer))
{
}

static void parse_mesh_matrix(vtk3DSImporter *vtkNotUsed(importer), vtk3DSMesh *vtkNotUsed(mesh))
{
  //  vtkGenericWarningMacro(<< "mesh matrix detected but not used\n");
}


static void parse_n_direct_light (vtk3DSImporter *importer, vtk3DSChunk *mainchunk)
{
  vtk3DSChunk chunk;
  vtk3DSSpotLight *s;
  vtk3DSOmniLight *o;
  int spot_flag = 0;

  read_point (importer, vtk3DS::pos);
  parse_colour (importer, &vtk3DS::col);

  do
  {
    start_chunk (importer, &chunk);

    if (chunk.end <= mainchunk->end)
    {
      switch (chunk.tag)
      {
        case 0x4620: break;
        case 0x4610: parse_dl_spotlight(importer);
          spot_flag = 1;
          break;
      }
    }

    end_chunk (importer, &chunk);
  } while (chunk.end <= mainchunk->end);

  if (!spot_flag)
  {
    o = (vtk3DSOmniLight *) VTK_LIST_FIND (importer->OmniList, vtk3DS::objName);

    if (o != nullptr)
    {
      vtk3DS::pos[0] = o->pos[0];
      vtk3DS::pos[1] = o->pos[1];
      vtk3DS::pos[2] = o->pos[2];
      vtk3DS::col    = o->col;
    }
    else
    {
      o = (vtk3DSOmniLight *) malloc (sizeof (*o));
      o->pos[0] = vtk3DS::pos[0];
      o->pos[1] = vtk3DS::pos[1];
      o->pos[2] = vtk3DS::pos[2];
      o->col = vtk3DS::col   ;
      strcpy (o->name, vtk3DS::objName);
      VTK_LIST_INSERT (importer->OmniList, o);
    }
  }
  else
  {
    s = (vtk3DSSpotLight *) VTK_LIST_FIND (importer->SpotLightList, vtk3DS::objName);

    if (s != nullptr)
    {
      vtk3DS::pos[0]    = s->pos[0];
      vtk3DS::pos[1]    = s->pos[1];
      vtk3DS::pos[2]    = s->pos[2];
      vtk3DS::target[0] = s->target[0];
      vtk3DS::target[1] = s->target[1];
      vtk3DS::target[2] = s->target[2];
      vtk3DS::col       = s->col;
      vtk3DS::hotspot   = s->hotspot;
      vtk3DS::falloff   = s->falloff;
    }
    else
    {
      if (vtk3DS::falloff <= 0.0)
      {
        vtk3DS::falloff = 180.0;
      }
      if (vtk3DS::hotspot <= 0.0)
      {
        vtk3DS::hotspot = 0.7*vtk3DS::falloff;
      }
      s = (vtk3DSSpotLight *) malloc (sizeof (*s));
      s->pos[0] = vtk3DS::pos[0];
      s->pos[1] = vtk3DS::pos[1];
      s->pos[2] = vtk3DS::pos[2];
      s->target[0] = vtk3DS::target[0];
      s->target[1] = vtk3DS::target[1];
      s->target[2] = vtk3DS::target[2];
      s->col = vtk3DS::col   ;
      s->hotspot = vtk3DS::hotspot;
      s->falloff = vtk3DS::falloff;
      strcpy (s->name, vtk3DS::objName);
      VTK_LIST_INSERT (importer->SpotLightList, s);
    }
  }
}


static void parse_dl_spotlight(vtk3DSImporter *importer)
{
  read_point (importer, vtk3DS::target);

  vtk3DS::hotspot = read_float(importer);
  vtk3DS::falloff = read_float(importer);
}


static void parse_n_camera(vtk3DSImporter *importer)
{
  float  bank;
  float  lens;
  vtk3DSCamera *c = (vtk3DSCamera *) malloc (sizeof (vtk3DSCamera));

  read_point (importer, vtk3DS::pos);
  read_point (importer, vtk3DS::target);
  bank = read_float(importer);
  lens = read_float(importer);

  strcpy (c->name, vtk3DS::objName);
  c->pos[0] = vtk3DS::pos[0];
  c->pos[1] = vtk3DS::pos[1];
  c->pos[2] = vtk3DS::pos[2];
  c->target[0] = vtk3DS::target[0];
  c->target[1] = vtk3DS::target[1];
  c->target[2] = vtk3DS::target[2];
  c->lens = lens;
  c->bank = bank;

  VTK_LIST_INSERT (importer->CameraList, c);
}

static void parse_colour (vtk3DSImporter *importer, vtk3DSColour *colour)
{
  vtk3DSChunk chunk;
  vtk3DSColour_24 colour_24;

  start_chunk (importer, &chunk);

  switch (chunk.tag)
  {
    case 0x0010: parse_colour_f (importer, colour);
      break;

    case 0x0011: parse_colour_24 (importer, &colour_24);
      colour->red   = colour_24.red/255.0;
      colour->green = colour_24.green/255.0;
      colour->blue  = colour_24.blue/255.0;
      break;

    default: vtkGenericWarningMacro(<< "Error parsing colour");
  }

  end_chunk (importer, &chunk);
}


static void parse_colour_f (vtk3DSImporter *importer, vtk3DSColour *colour)
{
  colour->red   = read_float(importer);
  colour->green = read_float(importer);
  colour->blue  = read_float(importer);
}


static void parse_colour_24 (vtk3DSImporter *importer, vtk3DSColour_24 *colour)
{
  colour->red   = read_byte(importer);
  colour->green = read_byte(importer);
  colour->blue  = read_byte(importer);
}


static float parse_percentage(vtk3DSImporter *importer)
{
  vtk3DSChunk chunk;
  float percent = 0.0;

  start_chunk (importer, &chunk);

  switch (chunk.tag)
  {
    case 0x0030: percent = parse_int_percentage(importer)/100.0;
      break;

    case 0x0031: percent = parse_float_percentage(importer);
      break;

    default:     vtkGenericWarningMacro( << "Error parsing percentage\n");
  }

  end_chunk (importer, &chunk);

  return percent;
}


static short parse_int_percentage(vtk3DSImporter *importer)
{
  word percent = read_word(importer);

  return percent;
}


static float parse_float_percentage(vtk3DSImporter *importer)
{
  float percent = read_float(importer);

  return percent;
}


static void start_chunk (vtk3DSImporter *importer, vtk3DSChunk *chunk)
{
  chunk->start  = ftell(importer->GetFileFD());
  chunk->tag    = peek_word(importer);
  chunk->length = peek_dword(importer);
  if (chunk->length == 0)
  {
    chunk->length = 1;
  }
  chunk->end    = chunk->start + chunk->length;
}


static void end_chunk (vtk3DSImporter *importer, vtk3DSChunk *chunk)
{
  fseek (importer->GetFileFD(), chunk->end, 0);
}


static byte read_byte(vtk3DSImporter *importer)
{
  byte data;

  data = fgetc (importer->GetFileFD());

  return data;
}


static word read_word(vtk3DSImporter *importer)
{
  word data;

  if (fread (&data, 2, 1, importer->GetFileFD()) != 1)
  {
    vtkErrorWithObjectMacro(
      importer, "Pre-mature end of file in read_word\n");
    data = 0;
  }
  vtkByteSwap::Swap2LE ((short *) &data);
  return data;
}

static word peek_word(vtk3DSImporter *importer)
{
  word data;

  if (fread (&data, 2, 1, importer->GetFileFD()) != 1)
  {
    data = 0;
  }
  vtkByteSwap::Swap2LE ((short *) &data);
  return data;
}

static dword peek_dword(vtk3DSImporter *importer)
{
  dword data;

  if (fread (&data, 4, 1, importer->GetFileFD()) != 1)
  {
    data = 0;
  }

  vtkByteSwap::Swap4LE ((char *) &data);
  return data;
}

static float read_float(vtk3DSImporter *importer)
{
  float data;

  if (fread (&data, 4, 1, importer->GetFileFD()) != 1)
  {
    vtkErrorWithObjectMacro(
      importer, "Pre-mature end of file in read_float\n");
    data = 0;
  }

  vtkByteSwap::Swap4LE ((char *) &data);
  return data;
}


static void read_point (vtk3DSImporter *importer, vtk3DSVector v)
{
  v[0] = read_float(importer);
  v[1] = read_float(importer);
  v[2] = read_float(importer);
}


static char *read_string(vtk3DSImporter *importer)
{
  static char string[80];
  int i;

  for (i = 0; i < 80; i++)
  {
    string[i] = read_byte(importer);

    if (string[i] == '\0')
    {
      break;
    }
  }

    return string;
}



static void cleanup_name (char *name)
{
  char *tmp = (char *) malloc (strlen(name)+2);
  int  i;

    /* Remove any leading blanks or quotes */
  i = 0;
  while ((name[i] == ' ' || name[i] == '"') && name[i] != '\0')
  {
    i++;
  }
  strcpy (tmp, &name[i]);

    /* Remove any trailing blanks or quotes */
  for (i = static_cast<int>(strlen(tmp))-1; i >= 0; i--)
  {
    if (isprint(tmp[i]) && !isspace(tmp[i]) && tmp[i] != '"')
    {
      break;
    }
    else
    {
      tmp[i] = '\0';
    }
  }

    strcpy (name, tmp);

    /* Prefix the letter 'N' to materials that begin with a digit */
    if (!isdigit (name[0]))
    {
      strcpy (tmp, name);
    }
    else
    {
      tmp[0] = 'N';
      strcpy (&tmp[1], name);
    }

    /* Replace all illegal characters in name with underscores */
    for (i = 0; tmp[i] != '\0'; i++)
    {
      if (!isalnum(tmp[i]))
      {
        tmp[i] = '_';
      }
    }

    strcpy (name, tmp);

    free (tmp);
}

vtk3DSImporter::~vtk3DSImporter()
{
  vtk3DSOmniLight *omniLight;
  vtk3DSSpotLight *spotLight;

  // walk the light list and delete vtk objects
  for (omniLight = this->OmniList; omniLight != (vtk3DSOmniLight *) nullptr; omniLight = (vtk3DSOmniLight *) omniLight->next)
  {
    omniLight->aLight->Delete();
  }
  VTK_LIST_KILL (this->OmniList);

  // walk the spot light list and delete vtk objects
  for (spotLight = this->SpotLightList; spotLight != (vtk3DSSpotLight *) nullptr;
       spotLight = (vtk3DSSpotLight *) spotLight->next)
  {
    spotLight->aLight->Delete();
  }
  VTK_LIST_KILL (this->SpotLightList);

  vtk3DSCamera *camera;
  // walk the camera list and delete vtk objects
  for (camera = this->CameraList; camera != (vtk3DSCamera *) nullptr;
       camera = (vtk3DSCamera *) camera->next)
  {
    camera->aCamera->Delete ();
  }
  VTK_LIST_KILL (this->CameraList);

  // walk the mesh list and delete malloced datra and vtk objects
  vtk3DSMesh *mesh;
  for (mesh = this->MeshList; mesh != (vtk3DSMesh *) nullptr;
       mesh = (vtk3DSMesh *) mesh->next)
  {
    if (mesh->anActor != nullptr)
    {
      mesh->anActor->Delete ();
    }
    if (mesh->aMapper != nullptr)
    {
      mesh->aMapper->Delete ();
    }
    if (mesh->aNormals != nullptr)
    {
      mesh->aNormals->Delete ();
    }
    if (mesh->aStripper != nullptr)
    {
      mesh->aStripper->Delete ();
    }
    if (mesh->aPoints != nullptr)
    {
      mesh->aPoints->Delete ();
    }
    if (mesh->aCellArray != nullptr)
    {
      mesh->aCellArray->Delete ();
    }
    if (mesh->aPolyData != nullptr)
    {
      mesh->aPolyData->Delete ();
    }
    if (mesh->vertex)
    {
      free (mesh->vertex);
    }
    if (mesh->face)
    {
      free (mesh->face);
    }
    if (mesh->mtl)
    {
      free (mesh->mtl);
    }
  }

  // then delete the list structure

  VTK_LIST_KILL (this->MeshList);
  VTK_LIST_KILL (this->MaterialList);

  // objects allocated in Material Property List
  vtk3DSMatProp *m;
  // just walk the list of material properties, deleting vtk properties
  for (m = this->MatPropList; m != (vtk3DSMatProp *) nullptr; m = (vtk3DSMatProp *) m->next)
  {
    m->aProperty->Delete();
  }

  // then delete the list structure
  VTK_LIST_KILL (this->MatPropList);

  delete [] this->FileName;
}

void vtk3DSImporter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";

  os << indent << "Compute Normals: "
     << (this->ComputeNormals ? "On\n" : "Off\n");
}






