/*
 * The 3D Studio File Format Library
 * Copyright (C) 1996-2001 by J.E. Hoffmann <je-h@gmx.net>
 * All rights reserved.
 *
 * This program is  free  software;  you can redistribute it and/or modify it
 * under the terms of the  GNU Lesser General Public License  as published by 
 * the  Free Software Foundation;  either version 2.1 of the License,  or (at 
 * your option) any later version.
 *
 * This  program  is  distributed in  the  hope that it will  be useful,  but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or  FITNESS FOR A  PARTICULAR PURPOSE.  See the  GNU Lesser General Public  
 * License for more details.
 *
 * You should  have received  a copy of the GNU Lesser General Public License
 * along with  this program;  if not, write to the  Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: file.c,v 1.23 2005/01/11 10:20:36 madmac Exp $
 */
#define LIB3DS_EXPORT
#include <lib3ds/file.h>
#include <lib3ds/chunk.h>
#include <lib3ds/io.h>
#include <lib3ds/material.h>
#include <lib3ds/mesh.h>
#include <lib3ds/camera.h>
#include <lib3ds/light.h>
#include <lib3ds/node.h>
#include <lib3ds/vector.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <config.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif



/*!
 * \defgroup file Files
 *
 * \author J.E. Hoffmann <je-h@gmx.net>
 */


static Lib3dsBool
fileio_error_func(void *self)
{
  FILE *f = (FILE*)self;
  return(ferror(f)!=0);
}


static long
fileio_seek_func(void *self, long offset, Lib3dsIoSeek origin)
{
  FILE *f = (FILE*)self;
  int o;
  switch (origin) {
    case LIB3DS_SEEK_SET:
      o = SEEK_SET;
      break;
    case LIB3DS_SEEK_CUR:
      o = SEEK_CUR;
      break;
    case LIB3DS_SEEK_END:
      o = SEEK_END;
      break;
    default:
      ASSERT(0);
      return(0);
  }
  return (fseek(f, offset, o));
}


static long
fileio_tell_func(void *self)
{
  FILE *f = (FILE*)self;
  return(ftell(f));
}


static int
fileio_read_func(void *self, Lib3dsByte *buffer, int size)
{
  FILE *f = (FILE*)self;
  return(fread(buffer, 1, size, f));
}


static int
fileio_write_func(void *self, const Lib3dsByte *buffer, int size)
{
  FILE *f = (FILE*)self;
  return(fwrite(buffer, 1, size, f));
}


/*!
 * Loads a .3DS file from disk into memory.
 *
 * \param filename  The filename of the .3DS file
 *
 * \return   A pointer to the Lib3dsFile structure containing the
 *           data of the .3DS file. 
 *           If the .3DS file can not be loaded NULL is returned.
 *
 * \note     To free the returned structure use lib3ds_free.
 *
 * \see lib3ds_file_save
 * \see lib3ds_file_new
 * \see lib3ds_file_free
 *
 * \ingroup file
 */
Lib3dsFile*
lib3ds_file_load(const char *filename)
{
  FILE *f;
  Lib3dsFile *file;
  Lib3dsIo *io;


  f = fopen(filename, "rb");
  if (!f) {
    return(0);
  }
  file = lib3ds_file_new();
  if (!file) {
    fclose(f);
    return(0);
  }
  io = lib3ds_io_new(
    f, 
    fileio_error_func,
    fileio_seek_func,
    fileio_tell_func,
    fileio_read_func,
    fileio_write_func
  );
  if (!io) {
    lib3ds_file_free(file);
    fclose(f);
    return(0);
  }

  if (!lib3ds_file_read(file, io)) {
    free(file);
    fclose(f);
    return(0);
  }

  lib3ds_io_free(io);
  fclose(f);
  return(file);
}


/*!
 * Saves a .3DS file from memory to disk.
 *
 * \param file      A pointer to a Lib3dsFile structure containing the
 *                  the data that should be stored.
 * \param filename  The filename of the .3DS file to store the data in.
 *
 * \return          TRUE on success, FALSE otherwise.
 *
 * \see lib3ds_file_load
 *
 * \ingroup file
 */
Lib3dsBool
lib3ds_file_save(Lib3dsFile *file, const char *filename)
{
  FILE *f;
  Lib3dsIo *io;
  Lib3dsBool result;

  f = fopen(filename, "wb");
  if (!f) {
    return(LIB3DS_FALSE);
  }
  io = lib3ds_io_new(
    f, 
    fileio_error_func,
    fileio_seek_func,
    fileio_tell_func,
    fileio_read_func,
    fileio_write_func
  );
  if (!io) {
    fclose(f);
    return LIB3DS_FALSE;
  }
  
  result = lib3ds_file_write(file, io);

  fclose(f);

  lib3ds_io_free(io);
  return(result);
}


/*!
 * Creates and returns a new, empty Lib3dsFile object.
 *
 * \return	A pointer to the Lib3dsFile structure.
 *		If the structure cannot be allocated, NULL is returned.
 *
 * \ingroup file
 */
Lib3dsFile*
lib3ds_file_new()
{
  Lib3dsFile *file;

  file=(Lib3dsFile*)calloc(sizeof(Lib3dsFile),1);
  if (!file) {
    return(0);
  }
  file->mesh_version=3;
  file->master_scale=1.0f;
  file->keyf_revision=5;
  strcpy(file->name, "LIB3DS");

  file->frames=100;
  file->segment_from=0;
  file->segment_to=100;
  file->current_frame=0;

  return(file);
}


/*!
 * Free a Lib3dsFile object and all of its resources.
 *
 * \param file The Lib3dsFile object to be freed.
 *
 * \ingroup file
 */
void
lib3ds_file_free(Lib3dsFile* file)
{
  ASSERT(file);
  lib3ds_viewport_set_views(&file->viewport,0);
  {
    Lib3dsMaterial *p,*q;
    
    for (p=file->materials; p; p=q) {
      q=p->next;
      lib3ds_material_free(p);
    }
    file->materials=0;
  }
  {
    Lib3dsCamera *p,*q;
    
    for (p=file->cameras; p; p=q) {
      q=p->next;
      lib3ds_camera_free(p);
    }
    file->cameras=0;
  }
  {
    Lib3dsLight *p,*q;
    
    for (p=file->lights; p; p=q) {
      q=p->next;
      lib3ds_light_free(p);
    }
    file->lights=0;
  }
  {
    Lib3dsMesh *p,*q;
    
    for (p=file->meshes; p; p=q) {
      q=p->next;
      lib3ds_mesh_free(p);
    }
    file->meshes=0;
  }
  {
    Lib3dsNode *p,*q;
  
    for (p=file->nodes; p; p=q) {
      q=p->next;
      lib3ds_node_free(p);
    }
  }
  free(file);
}


/*!
 * Evaluate all of the nodes in this Lib3dsFile object.
 *
 * \param file The Lib3dsFile object to be evaluated.
 * \param t time value, between 0. and file->frames
 *
 * \see lib3ds_node_eval
 *
 * \ingroup file
 */
void
lib3ds_file_eval(Lib3dsFile *file, Lib3dsFloat t)
{
  Lib3dsNode *p;

  for (p=file->nodes; p!=0; p=p->next) {
    lib3ds_node_eval(p, t);
  }
}


static Lib3dsBool
named_object_read(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;
  char name[64];
  Lib3dsWord chunk;

  if (!lib3ds_chunk_read_start(&c, LIB3DS_NAMED_OBJECT, io)) {
    return(LIB3DS_FALSE);
  }
  if (!lib3ds_io_read_string(io, name, 64)) {
    return(LIB3DS_FALSE);
  }
  lib3ds_chunk_dump_info("  NAME=%s", name);
  lib3ds_chunk_read_tell(&c, io);

  while ((chunk=lib3ds_chunk_read_next(&c, io))!=0) {
    switch (chunk) {
      case LIB3DS_N_TRI_OBJECT:
        {
          Lib3dsMesh *mesh;

          mesh=lib3ds_mesh_new(name);
          if (!mesh) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_mesh_read(mesh, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_mesh(file, mesh);
        }
        break;
      case LIB3DS_N_CAMERA:
        {
          Lib3dsCamera *camera;

          camera=lib3ds_camera_new(name);
          if (!camera) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_camera_read(camera, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_camera(file, camera);
        }
        break;
      case LIB3DS_N_DIRECT_LIGHT:
        {
          Lib3dsLight *light;

          light=lib3ds_light_new(name);
          if (!light) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_light_read(light, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_light(file, light);
        }
        break;
      default:
        lib3ds_chunk_unknown(chunk);
    }
  }
  
  lib3ds_chunk_read_end(&c, io);
  return(LIB3DS_TRUE);
}


static Lib3dsBool
ambient_read(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;
  Lib3dsWord chunk;
  Lib3dsBool have_lin=LIB3DS_FALSE;

  if (!lib3ds_chunk_read_start(&c, LIB3DS_AMBIENT_LIGHT, io)) {
    return(LIB3DS_FALSE);
  }

  while ((chunk=lib3ds_chunk_read_next(&c, io))!=0) {
    switch (chunk) {
      case LIB3DS_LIN_COLOR_F:
        {
          int i;
          for (i=0; i<3; ++i) {
            file->ambient[i]=lib3ds_io_read_float(io);
          }
        }
        have_lin=LIB3DS_TRUE;
        break;
      case LIB3DS_COLOR_F:
        {
          /* gamma corrected color chunk
             replaced in 3ds R3 by LIN_COLOR_24 */
          if (!have_lin) {
            int i;
            for (i=0; i<3; ++i) {
              file->ambient[i]=lib3ds_io_read_float(io);
            }
          }
        }
        break;
      default:
        lib3ds_chunk_unknown(chunk);
    }
  }
  
  lib3ds_chunk_read_end(&c, io);
  return(LIB3DS_TRUE);
}


static Lib3dsBool
mdata_read(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;
  Lib3dsWord chunk;

  if (!lib3ds_chunk_read_start(&c, LIB3DS_MDATA, io)) {
    return(LIB3DS_FALSE);
  }
  
  while ((chunk=lib3ds_chunk_read_next(&c, io))!=0) {
    switch (chunk) {
      case LIB3DS_MESH_VERSION:
        {
          file->mesh_version=lib3ds_io_read_intd(io);
        }
        break;
      case LIB3DS_MASTER_SCALE:
        {
          file->master_scale=lib3ds_io_read_float(io);
        }
        break;
      case LIB3DS_SHADOW_MAP_SIZE:
      case LIB3DS_LO_SHADOW_BIAS:
      case LIB3DS_HI_SHADOW_BIAS:
      case LIB3DS_SHADOW_SAMPLES:
      case LIB3DS_SHADOW_RANGE:
      case LIB3DS_SHADOW_FILTER:
      case LIB3DS_RAY_BIAS:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_shadow_read(&file->shadow, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_VIEWPORT_LAYOUT:
      case LIB3DS_DEFAULT_VIEW:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_viewport_read(&file->viewport, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_O_CONSTS:
        {
          int i;
          for (i=0; i<3; ++i) {
            file->construction_plane[i]=lib3ds_io_read_float(io);
          }
        }
        break;
      case LIB3DS_AMBIENT_LIGHT:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!ambient_read(file, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_BIT_MAP:
      case LIB3DS_SOLID_BGND:
      case LIB3DS_V_GRADIENT:
      case LIB3DS_USE_BIT_MAP:
      case LIB3DS_USE_SOLID_BGND:
      case LIB3DS_USE_V_GRADIENT:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_background_read(&file->background, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_FOG:
      case LIB3DS_LAYER_FOG:
      case LIB3DS_DISTANCE_CUE:
      case LIB3DS_USE_FOG:
      case LIB3DS_USE_LAYER_FOG:
      case LIB3DS_USE_DISTANCE_CUE:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_atmosphere_read(&file->atmosphere, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_MAT_ENTRY:
        {
          Lib3dsMaterial *material;

          material=lib3ds_material_new();
          if (!material) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_material_read(material, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_material(file, material);
        }
        break;
      case LIB3DS_NAMED_OBJECT:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!named_object_read(file, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      default:
        lib3ds_chunk_unknown(chunk);
    }
  }

  lib3ds_chunk_read_end(&c, io);
  return(LIB3DS_TRUE);
}


static Lib3dsBool
kfdata_read(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;
  Lib3dsWord chunk;

  if (!lib3ds_chunk_read_start(&c, LIB3DS_KFDATA, io)) {
    return(LIB3DS_FALSE);
  }
  
  while ((chunk=lib3ds_chunk_read_next(&c, io))!=0) {
    switch (chunk) {
      case LIB3DS_KFHDR:
        {
          file->keyf_revision=lib3ds_io_read_word(io);
          if (!lib3ds_io_read_string(io, file->name, 12+1)) {
            return(LIB3DS_FALSE);
          }
          file->frames=lib3ds_io_read_intd(io);
        }
        break;
      case LIB3DS_KFSEG:
        {
          file->segment_from=lib3ds_io_read_intd(io);
          file->segment_to=lib3ds_io_read_intd(io);
        }
        break;
      case LIB3DS_KFCURTIME:
        {
          file->current_frame=lib3ds_io_read_intd(io);
        }
        break;
      case LIB3DS_VIEWPORT_LAYOUT:
      case LIB3DS_DEFAULT_VIEW:
        {
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_viewport_read(&file->viewport_keyf, io)) {
            return(LIB3DS_FALSE);
          }
        }
        break;
      case LIB3DS_AMBIENT_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_ambient();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      case LIB3DS_OBJECT_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_object();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      case LIB3DS_CAMERA_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_camera();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      case LIB3DS_TARGET_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_target();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      case LIB3DS_LIGHT_NODE_TAG:
      case LIB3DS_SPOTLIGHT_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_light();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      case LIB3DS_L_TARGET_NODE_TAG:
        {
          Lib3dsNode *node;

          node=lib3ds_node_new_spot();
          if (!node) {
            return(LIB3DS_FALSE);
          }
          lib3ds_chunk_read_reset(&c, io);
          if (!lib3ds_node_read(node, file, io)) {
            return(LIB3DS_FALSE);
          }
          lib3ds_file_insert_node(file, node);
        }
        break;
      default:
        lib3ds_chunk_unknown(chunk);
    }
  }

  lib3ds_chunk_read_end(&c, io);
  return(LIB3DS_TRUE);
}


/*!
 * Read 3ds file data into a Lib3dsFile object.
 *
 * \param file The Lib3dsFile object to be filled.
 * \param io A Lib3dsIo object previously set up by the caller.
 *
 * \return LIB3DS_TRUE on success, LIB3DS_FALSE on failure.
 *
 * \ingroup file
 */
Lib3dsBool
lib3ds_file_read(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;
  Lib3dsWord chunk;

  if (!lib3ds_chunk_read_start(&c, 0, io)) {
    return(LIB3DS_FALSE);
  }
  switch (c.chunk) {
    case LIB3DS_MDATA:
      {
        lib3ds_chunk_read_reset(&c, io);
        if (!mdata_read(file, io)) {
          return(LIB3DS_FALSE);
        }
      }
      break;
    case LIB3DS_M3DMAGIC:
    case LIB3DS_MLIBMAGIC:
    case LIB3DS_CMAGIC:
      {
        while ((chunk=lib3ds_chunk_read_next(&c, io))!=0) {
          switch (chunk) {
            case LIB3DS_M3D_VERSION:
              {
                file->mesh_version=lib3ds_io_read_dword(io);
              }
              break;
            case LIB3DS_MDATA:
              {
                lib3ds_chunk_read_reset(&c, io);
                if (!mdata_read(file, io)) {
                  return(LIB3DS_FALSE);
                }
              }
              break;
            case LIB3DS_KFDATA:
              {
                lib3ds_chunk_read_reset(&c, io);
                if (!kfdata_read(file, io)) {
                  return(LIB3DS_FALSE);
                }
              }
              break;
            default:
              lib3ds_chunk_unknown(chunk);
          }
        }
      }
      break;
    default:
      lib3ds_chunk_unknown(c.chunk);
      return(LIB3DS_FALSE);
  }

  lib3ds_chunk_read_end(&c, io);
  return(LIB3DS_TRUE);
}


static Lib3dsBool
colorf_write(Lib3dsRgba rgb, Lib3dsIo *io)
{
  Lib3dsChunk c;

  c.chunk=LIB3DS_COLOR_F;
  c.size=18;
  lib3ds_chunk_write(&c,io);
  lib3ds_io_write_rgb(io, rgb);

  c.chunk=LIB3DS_LIN_COLOR_F;
  c.size=18;
  lib3ds_chunk_write(&c,io);
  lib3ds_io_write_rgb(io, rgb);
  return(LIB3DS_TRUE);
}


static Lib3dsBool
mdata_write(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;

  c.chunk=LIB3DS_MDATA;
  if (!lib3ds_chunk_write_start(&c,io)) {
    return(LIB3DS_FALSE);
  }

  { /*---- LIB3DS_MESH_VERSION ----*/
    Lib3dsChunk c;
    c.chunk=LIB3DS_MESH_VERSION;
    c.size=10;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_intd(io, file->mesh_version);
  }
  { /*---- LIB3DS_MASTER_SCALE ----*/
    Lib3dsChunk c;
    c.chunk=LIB3DS_MASTER_SCALE;
    c.size=10;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_float(io, file->master_scale);
  }
  { /*---- LIB3DS_O_CONSTS ----*/
    int i;
    for (i=0; i<3; ++i) {
      if (fabs(file->construction_plane[i])>LIB3DS_EPSILON) {
        break;
      }
    }
    if (i<3) {
      Lib3dsChunk c;
      c.chunk=LIB3DS_O_CONSTS;
      c.size=18;
      lib3ds_chunk_write(&c,io);
      lib3ds_io_write_vector(io, file->construction_plane);
    }
  }
  
  { /*---- LIB3DS_AMBIENT_LIGHT ----*/
    int i;
    for (i=0; i<3; ++i) {
      if (fabs(file->ambient[i])>LIB3DS_EPSILON) {
        break;
      }
    }
    if (i<3) {
      Lib3dsChunk c;
      c.chunk=LIB3DS_AMBIENT_LIGHT;
      c.size=42;
      lib3ds_chunk_write(&c,io);
      colorf_write(file->ambient,io);
    }
  }
  lib3ds_background_write(&file->background, io);
  lib3ds_atmosphere_write(&file->atmosphere, io);
  lib3ds_shadow_write(&file->shadow, io);
  lib3ds_viewport_write(&file->viewport, io);
  {
    Lib3dsMaterial *p;
    for (p=file->materials; p!=0; p=p->next) {
      if (!lib3ds_material_write(p,io)) {
        return(LIB3DS_FALSE);
      }
    }
  }
  {
    Lib3dsCamera *p;
    Lib3dsChunk c;
    
    for (p=file->cameras; p!=0; p=p->next) {
      c.chunk=LIB3DS_NAMED_OBJECT;
      if (!lib3ds_chunk_write_start(&c,io)) {
        return(LIB3DS_FALSE);
      }
      lib3ds_io_write_string(io, p->name);
      lib3ds_camera_write(p,io);
      if (!lib3ds_chunk_write_end(&c,io)) {
        return(LIB3DS_FALSE);
      }
    }
  }
  {
    Lib3dsLight *p;
    Lib3dsChunk c;
    
    for (p=file->lights; p!=0; p=p->next) {
      c.chunk=LIB3DS_NAMED_OBJECT;
      if (!lib3ds_chunk_write_start(&c,io)) {
        return(LIB3DS_FALSE);
      }
      lib3ds_io_write_string(io,p->name);
      lib3ds_light_write(p,io);
      if (!lib3ds_chunk_write_end(&c,io)) {
        return(LIB3DS_FALSE);
      }
    }
  }
  {
    Lib3dsMesh *p;
    Lib3dsChunk c;
    
    for (p=file->meshes; p!=0; p=p->next) {
      c.chunk=LIB3DS_NAMED_OBJECT;
      if (!lib3ds_chunk_write_start(&c,io)) {
        return(LIB3DS_FALSE);
      }
      lib3ds_io_write_string(io, p->name);
      lib3ds_mesh_write(p,io);
      if (!lib3ds_chunk_write_end(&c,io)) {
        return(LIB3DS_FALSE);
      }
    }
  }

  if (!lib3ds_chunk_write_end(&c,io)) {
    return(LIB3DS_FALSE);
  }
  return(LIB3DS_TRUE);
}



static Lib3dsBool
nodes_write(Lib3dsNode *node, Lib3dsFile *file, Lib3dsIo *io)
{
  {
    Lib3dsNode *p;
    for (p=node->childs; p!=0; p=p->next) {
      if (!lib3ds_node_write(p, file, io)) {
        return(LIB3DS_FALSE);
      }
      nodes_write(p, file, io);
    }
  }
  return(LIB3DS_TRUE);
}


static Lib3dsBool
kfdata_write(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;

  if (!file->nodes) {
    return(LIB3DS_TRUE);
  }
  
  c.chunk=LIB3DS_KFDATA;
  if (!lib3ds_chunk_write_start(&c,io)) {
    return(LIB3DS_FALSE);
  }
  
  { /*---- LIB3DS_KFHDR ----*/
    Lib3dsChunk c;
    c.chunk=LIB3DS_KFHDR;
    c.size=6 + 2 + strlen(file->name)+1 +4;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_intw(io, file->keyf_revision);
    lib3ds_io_write_string(io, file->name);
    lib3ds_io_write_intd(io, file->frames);
  }
  { /*---- LIB3DS_KFSEG ----*/
    Lib3dsChunk c;
    c.chunk=LIB3DS_KFSEG;
    c.size=14;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_intd(io, file->segment_from);
    lib3ds_io_write_intd(io, file->segment_to);
  }
  { /*---- LIB3DS_KFCURTIME ----*/
    Lib3dsChunk c;
    c.chunk=LIB3DS_KFCURTIME;
    c.size=10;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_intd(io, file->current_frame);
  }
  lib3ds_viewport_write(&file->viewport_keyf, io);
  
  {
    Lib3dsNode *p;
    for (p=file->nodes; p!=0; p=p->next) {
      if (!lib3ds_node_write(p, file, io)) {
        return(LIB3DS_FALSE);
      }
      if (!nodes_write(p, file, io)) {
        return(LIB3DS_FALSE);
      }
    }
  }
  
  if (!lib3ds_chunk_write_end(&c,io)) {
    return(LIB3DS_FALSE);
  }
  return(LIB3DS_TRUE);
}


/*!
 * Write 3ds file data from a Lib3dsFile object to a file.
 *
 * \param file The Lib3dsFile object to be written.
 * \param io A Lib3dsIo object previously set up by the caller.
 *
 * \return LIB3DS_TRUE on success, LIB3DS_FALSE on failure.
 *
 * \ingroup file
 */
Lib3dsBool
lib3ds_file_write(Lib3dsFile *file, Lib3dsIo *io)
{
  Lib3dsChunk c;

  c.chunk=LIB3DS_M3DMAGIC;
  if (!lib3ds_chunk_write_start(&c,io)) {
    LIB3DS_ERROR_LOG;
    return(LIB3DS_FALSE);
  }

  { /*---- LIB3DS_M3D_VERSION ----*/
    Lib3dsChunk c;

    c.chunk=LIB3DS_M3D_VERSION;
    c.size=10;
    lib3ds_chunk_write(&c,io);
    lib3ds_io_write_dword(io, file->mesh_version);
  }

  if (!mdata_write(file, io)) {
    return(LIB3DS_FALSE);
  }
  if (!kfdata_write(file, io)) {
    return(LIB3DS_FALSE);
  }

  if (!lib3ds_chunk_write_end(&c,io)) {
    return(LIB3DS_FALSE);
  }
  return(LIB3DS_TRUE);
}


/*!
 * Insert a new Lib3dsMaterial object into the materials list of
 * a Lib3dsFile object.
 *
 * The new Lib3dsMaterial object is inserted into the materials list
 * in alphabetic order by name.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsMaterial object to be inserted into file->materials
 *
 * \ingroup file
 */
void
lib3ds_file_insert_material(Lib3dsFile *file, Lib3dsMaterial *material)
{
  Lib3dsMaterial *p,*q;
  
  ASSERT(file);
  ASSERT(material);
  ASSERT(!material->next);

  q=0;
  for (p=file->materials; p!=0; p=p->next) {
    if (strcmp(material->name, p->name)<0) {
      break;
    }
    q=p;
  }
  if (!q) {
    material->next=file->materials;
    file->materials=material;
  }
  else {
    material->next=q->next;
    q->next=material;
  }
}


/*!
 * Remove a Lib3dsMaterial object from the materials list of
 * a Lib3dsFile object.
 *
 * If the Lib3dsMaterial is not found in the materials list, nothing is
 * done (except that an error log message may be generated.)
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsMaterial object to be removed from file->materials
 *
 * \ingroup file
 */
void
lib3ds_file_remove_material(Lib3dsFile *file, Lib3dsMaterial *material)
{
  Lib3dsMaterial *p,*q;

  ASSERT(file);
  ASSERT(material);
  ASSERT(file->materials);
  for (p=0,q=file->materials; q; p=q,q=q->next) {
    if (q==material) {
      break;
    }
  }
  if (!q) {
    ASSERT(LIB3DS_FALSE);
    return;
  }
  if (!p) {
    file->materials=material->next;
  }
  else {
    p->next=q->next;
  }
  material->next=0;
}


/*!
 * Return a Lib3dsMaterial object by name.
 *
 * \param file Lib3dsFile object to be searched.
 * \param name Name of the Lib3dsMaterial object to be searched for.
 *
 * \return A pointer to the named Lib3dsMaterial, or NULL if not found.
 *
 * \ingroup file
 */
Lib3dsMaterial*
lib3ds_file_material_by_name(Lib3dsFile *file, const char *name)
{
  Lib3dsMaterial *p;

  ASSERT(file);
  for (p=file->materials; p!=0; p=p->next) {
    if (strcmp(p->name,name)==0) {
      return(p);
    }
  }
  return(0);
}


/*!
 * Dump all Lib3dsMaterial objects found in a Lib3dsFile object.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_material_dump
 *
 * \ingroup file
 */
void
lib3ds_file_dump_materials(Lib3dsFile *file)
{
  Lib3dsMaterial *p;

  ASSERT(file);
  for (p=file->materials; p!=0; p=p->next) {
    lib3ds_material_dump(p);
  }
}


/*!
 * Insert a new Lib3dsMesh object into the meshes list of
 * a Lib3dsFile object.
 *
 * The new Lib3dsMesh object is inserted into the meshes list
 * in alphabetic order by name.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsMesh object to be inserted into file->meshes
 *
 * \ingroup file
 */
void
lib3ds_file_insert_mesh(Lib3dsFile *file, Lib3dsMesh *mesh)
{
  Lib3dsMesh *p,*q;
  
  ASSERT(file);
  ASSERT(mesh);
  ASSERT(!mesh->next);

  q=0;
  for (p=file->meshes; p!=0; p=p->next) {
    if (strcmp(mesh->name, p->name)<0) {
      break;
    }
    q=p;
  }
  if (!q) {
    mesh->next=file->meshes;
    file->meshes=mesh;
  }
  else {
    mesh->next=q->next;
    q->next=mesh;
  }
}


/*!
 * Remove a Lib3dsMesh object from the meshes list of
 * a Lib3dsFile object.
 *
 * If the Lib3dsMesh is not found in the meshes list, nothing is done
 * (except that an error log message may be generated.)
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsMesh object to be removed from file->meshes
 *
 * \ingroup file
 */
void
lib3ds_file_remove_mesh(Lib3dsFile *file, Lib3dsMesh *mesh)
{
  Lib3dsMesh *p,*q;

  ASSERT(file);
  ASSERT(mesh);
  ASSERT(file->meshes);
  for (p=0,q=file->meshes; q; p=q,q=q->next) {
    if (q==mesh) {
      break;
    }
  }
  if (!q) {
    ASSERT(LIB3DS_FALSE);
    return;
  }
  if (!p) {
    file->meshes=mesh->next;
  }
  else {
    p->next=q->next;
  }
  mesh->next=0;
}


/*!
 * Return a Lib3dsMesh object from a Lib3dsFile by name.
 *
 * \param file Lib3dsFile object to be searched.
 * \param name Name of the Lib3dsMesh object to be searched for.
 *
 * \return A pointer to the named Lib3dsMesh, or NULL if not found.
 *
 * \ingroup file
 */
Lib3dsMesh*
lib3ds_file_mesh_by_name(Lib3dsFile *file, const char *name)
{
  Lib3dsMesh *p;

  ASSERT(file);
  for (p=file->meshes; p!=0; p=p->next) {
    if (strcmp(p->name,name)==0) {
      return(p);
    }
  }
  return(0);
}


/*!
 * Dump all Lib3dsMesh objects found in a Lib3dsFile object.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_mesh_dump
 *
 * \ingroup file
 */
void
lib3ds_file_dump_meshes(Lib3dsFile *file)
{
  Lib3dsMesh *p;

  ASSERT(file);
  for (p=file->meshes; p!=0; p=p->next) {
    lib3ds_mesh_dump(p);
  }
}


static void
dump_instances(Lib3dsNode *node, const char* parent)
{
  Lib3dsNode *p;
  char name[255];

  ASSERT(node);
  ASSERT(parent);
  strcpy(name, parent);
  strcat(name, ".");
  strcat(name, node->name);
  if (node->type==LIB3DS_OBJECT_NODE) {
    printf("  %s : %s\n", name, node->data.object.instance);
  }
  for (p=node->childs; p!=0; p=p->next) {
    dump_instances(p, parent);
  }
}


/*!
 * Dump all Lib3dsNode object names found in a Lib3dsFile object.
 *
 * For each node of type OBJECT_NODE, its name and data.object.instance
 * fields are printed to stdout.  Consider using lib3ds_file_dump_nodes()
 * instead, as that function dumps more information.
 *
 * Nodes are dumped recursively.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_file_dump_nodes
 *
 * \ingroup file
 */
void
lib3ds_file_dump_instances(Lib3dsFile *file)
{
  Lib3dsNode *p;

  ASSERT(file);
  for (p=file->nodes; p!=0; p=p->next) {
    dump_instances(p,"");
  }
}


/*!
 * Insert a new Lib3dsCamera object into the cameras list of
 * a Lib3dsFile object.
 *
 * The new Lib3dsCamera object is inserted into the cameras list
 * in alphabetic order by name.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsCamera object to be inserted into file->cameras
 *
 * \ingroup file
 */
void
lib3ds_file_insert_camera(Lib3dsFile *file, Lib3dsCamera *camera)
{
  Lib3dsCamera *p,*q;
  
  ASSERT(file);
  ASSERT(camera);
  ASSERT(!camera->next);

  q=0;
  for (p=file->cameras; p!=0; p=p->next) {
    if (strcmp(camera->name, p->name)<0) {
      break;
    }
    q=p;
  }
  if (!q) {
    camera->next=file->cameras;
    file->cameras=camera;
  }
  else {
    camera->next=q->next;
    q->next=camera;
  }
}


/*!
 * Remove a Lib3dsCamera object from the cameras list of
 * a Lib3dsFile object.
 *
 * If the Lib3dsCamera is not found in the cameras list, nothing is done
 * (except that an error log message may be generated.)
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsCamera object to be removed from file->cameras
 *
 * \ingroup file
 */
void
lib3ds_file_remove_camera(Lib3dsFile *file, Lib3dsCamera *camera)
{
  Lib3dsCamera *p,*q;

  ASSERT(file);
  ASSERT(camera);
  ASSERT(file->cameras);
  for (p=0,q=file->cameras; q; p=q,q=q->next) {
    if (q==camera) {
      break;
    }
  }
  if (!q) {
    ASSERT(LIB3DS_FALSE);
    return;
  }
  if (!p) {
    file->cameras=camera->next;
  }
  else {
    p->next=q->next;
  }
  camera->next=0;
}


/*!
 * Return a Lib3dsCamera object from a Lib3dsFile by name.
 *
 * \param file Lib3dsFile object to be searched.
 * \param name Name of the Lib3dsCamera object to be searched for.
 *
 * \return A pointer to the named Lib3dsCamera, or NULL if not found.
 *
 * \ingroup file
 */
Lib3dsCamera*
lib3ds_file_camera_by_name(Lib3dsFile *file, const char *name)
{
  Lib3dsCamera *p;

  ASSERT(file);
  for (p=file->cameras; p!=0; p=p->next) {
    if (strcmp(p->name,name)==0) {
      return(p);
    }
  }
  return(0);
}


/*!
 * Dump all Lib3dsCamera objects found in a Lib3dsFile object.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_camera_dump
 *
 * \ingroup file
 */
void
lib3ds_file_dump_cameras(Lib3dsFile *file)
{
  Lib3dsCamera *p;

  ASSERT(file);
  for (p=file->cameras; p!=0; p=p->next) {
    lib3ds_camera_dump(p);
  }
}


/*!
 * Insert a new Lib3dsLight object into the lights list of
 * a Lib3dsFile object.
 *
 * The new Lib3dsLight object is inserted into the lights list
 * in alphabetic order by name.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsLight object to be inserted into file->lights
 *
 * \ingroup file
 */
void
lib3ds_file_insert_light(Lib3dsFile *file, Lib3dsLight *light)
{
  Lib3dsLight *p,*q;
  
  ASSERT(file);
  ASSERT(light);
  ASSERT(!light->next);

  q=0;
  for (p=file->lights; p!=0; p=p->next) {
    if (strcmp(light->name, p->name)<0) {
      break;
    }
    q=p;
  }
  if (!q) {
    light->next=file->lights;
    file->lights=light;
  }
  else {
    light->next=q->next;
    q->next=light;
  }
}


/*!
 * Remove a Lib3dsLight object from the lights list of
 * a Lib3dsFile object.
 *
 * If the Lib3dsLight is not found in the lights list, nothing is done
 * (except that an error log message may be generated.)
 *
 * \param file The Lib3dsFile object to be modified.
 * \param material The Lib3dsLight object to be removed from file->lights
 *
 * \ingroup file
 */
void
lib3ds_file_remove_light(Lib3dsFile *file, Lib3dsLight *light)
{
  Lib3dsLight *p,*q;

  ASSERT(file);
  ASSERT(light);
  ASSERT(file->lights);
  for (p=0,q=file->lights; q; p=q,q=q->next) {
    if (q==light) {
      break;
    }
  }
  if (!q) {
    ASSERT(LIB3DS_FALSE);
    return;
  }
  if (!p) {
    file->lights=light->next;
  }
  else {
    p->next=q->next;
  }
  light->next=0;
}


/*!
 * Return a Lib3dsLight object from a Lib3dsFile by name.
 *
 * \param file Lib3dsFile object to be searched.
 * \param name Name of the Lib3dsLight object to be searched for.
 *
 * \return A pointer to the named Lib3dsLight, or NULL if not found.
 *
 * \ingroup file
 */
Lib3dsLight*
lib3ds_file_light_by_name(Lib3dsFile *file, const char *name)
{
  Lib3dsLight *p;

  ASSERT(file);
  for (p=file->lights; p!=0; p=p->next) {
    if (strcmp(p->name,name)==0) {
      return(p);
    }
  }
  return(0);
}


/*!
 * Dump all Lib3dsLight objects found in a Lib3dsFile object.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_light_dump
 *
 * \ingroup file
 */
void
lib3ds_file_dump_lights(Lib3dsFile *file)
{
  Lib3dsLight *p;

  ASSERT(file);
  for (p=file->lights; p!=0; p=p->next) {
    lib3ds_light_dump(p);
  }
}


/*!
 * Compute the bounding box for Lib3dsFile objects.
 *
 * This function computes the bounding box for all meshes
 * in the Lib3dsFile object.  Cameras and lights are not included.
 *
 * \param file The Lib3dsFile object to be examined.
 * \param min Returned minimum x,y,z values.
 * \param max Returned maximum x,y,z values.
 *
 * \ingroup file
 */
void
lib3ds_object_bounding_box(Lib3dsFile *file, Lib3dsVector min, Lib3dsVector max)
{
  {
    Lib3dsVector lmin, lmax;
    Lib3dsMesh *p=file->meshes;

    if (p) {
      lib3ds_mesh_bounding_box(p, min, max);
      p = p->next;  
    }
    while (p) {
      lib3ds_mesh_bounding_box(p, lmin, lmax);
      lib3ds_vector_min(min, lmin);
      lib3ds_vector_max(max, lmax);
      p=p->next;
    }
  }
}  


/*!
 * Compute the bounding box for a Lib3dsFile.
 *
 * This function computes the bounding box for all meshes, cameras,
 * and lights in the Lib3dsFile object.
 *
 * \param file The Lib3dsFile object to be examined.
 * \param min Returned minimum x,y,z values.
 * \param max Returned maximum x,y,z values.
 *
 * \ingroup file
 */
void
lib3ds_file_bounding_box(Lib3dsFile *file, Lib3dsVector min, Lib3dsVector max)
{
  Lib3dsBool init=LIB3DS_FALSE;

  {
    Lib3dsVector lmin, lmax;
    Lib3dsMesh *p=file->meshes;

    if (!init && p) {
      init = LIB3DS_TRUE;
      lib3ds_mesh_bounding_box(p, min, max);
      p = p->next;  
    }
    while (p) {
      lib3ds_mesh_bounding_box(p, lmin, lmax);
      lib3ds_vector_min(min, lmin);
      lib3ds_vector_max(max, lmax);
      p=p->next;
    }
  }
  {
    Lib3dsCamera *p=file->cameras;
    if (!init && p) {
      init = LIB3DS_TRUE;
      lib3ds_vector_copy(min, p->position);
      lib3ds_vector_copy(max, p->position);
    }

    while (p) {
      lib3ds_vector_min(min, p->position);
      lib3ds_vector_max(max, p->position);
      lib3ds_vector_min(min, p->target);
      lib3ds_vector_max(max, p->target);
      p=p->next;
    }
  }
  {
    Lib3dsLight *p=file->lights;
    if (!init && p) {
      init = LIB3DS_TRUE;
      lib3ds_vector_copy(min, p->position);
      lib3ds_vector_copy(max, p->position);
    }

    while (p) {
      lib3ds_vector_min(min, p->position);
      lib3ds_vector_max(max, p->position);
      if (p->spot_light) {
        lib3ds_vector_min(min, p->spot);
        lib3ds_vector_max(max, p->spot);
      }
      p=p->next;
    }
  }
}  


/*!
 * Return a node object by name and type.
 *
 * This function performs a recursive search for the specified node.
 * Both name and type must match.
 *
 * \param file The Lib3dsFile to be searched.
 * \param name The target node name.
 * \param type The target node type
 *
 * \return A pointer to the first matching node, or NULL if not found.
 *
 * \see lib3ds_node_by_name
 *
 * \ingroup file
 */
Lib3dsNode*
lib3ds_file_node_by_name(Lib3dsFile *file, const char* name, Lib3dsNodeTypes type)
{
  Lib3dsNode *p,*q;

  ASSERT(file);
  for (p=file->nodes; p!=0; p=p->next) {
    if ((p->type==type) && (strcmp(p->name, name)==0)) {
      return(p);
    }
    q=lib3ds_node_by_name(p, name, type);
    if (q) {
      return(q);
    }
  }
  return(0);
}


/*!
 * Return a node object by id.
 *
 * This function performs a recursive search for the specified node.
 *
 * \param file The Lib3dsFile to be searched.
 * \param node_id The target node id.
 *
 * \return A pointer to the first matching node, or NULL if not found.
 *
 * \see lib3ds_node_by_id
 *
 * \ingroup file
 */
Lib3dsNode*
lib3ds_file_node_by_id(Lib3dsFile *file, Lib3dsWord node_id)
{
  Lib3dsNode *p,*q;

  ASSERT(file);
  for (p=file->nodes; p!=0; p=p->next) {
    if (p->node_id==node_id) {
      return(p);
    }
    q=lib3ds_node_by_id(p, node_id);
    if (q) {
      return(q);
    }
  }
  return(0);
}


/*!
 * Insert a new node into a Lib3dsFile object.
 *
 * If the node's parent_id structure is not LIB3DS_NO_PARENT and the
 * specified parent is found inside the Lib3dsFile object, then the
 * node is inserted as a child of that parent.  If the parent_id
 * structure is LIB3DS_NO_PARENT or the specified parent is not found,
 * then the node is inserted at the top level.
 *
 * Node is inserted in alphabetic order by name.
 *
 * Finally, if any other top-level nodes in file specify this node as
 * their parent, they are relocated as a child of this node.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param node The node to be inserted into file
 *
 * \ingroup file
 */
void
lib3ds_file_insert_node(Lib3dsFile *file, Lib3dsNode *node)
{
  Lib3dsNode *parent,*p,*n;
  
  ASSERT(node);
  ASSERT(!node->next);
  ASSERT(!node->parent);

  parent=0;
  if (node->parent_id!=LIB3DS_NO_PARENT) {
    parent=lib3ds_file_node_by_id(file, node->parent_id);
  }
  node->parent=parent;
  
  if (!parent) {
    for (p=0,n=file->nodes; n!=0; p=n,n=n->next) {
      if (strcmp(n->name, node->name)>0) {
        break;
      }
    }
    if (!p) {
      node->next=file->nodes;
      file->nodes=node;
    }
    else {
      node->next=p->next;
      p->next=node;
    }
  }
  else {
    for (p=0,n=parent->childs; n!=0; p=n,n=n->next) {
      if (strcmp(n->name, node->name)>0) {
        break;
      }
    }
    if (!p) {
      node->next=parent->childs;
      parent->childs=node;
    }
    else {
      node->next=p->next;
      p->next=node;
    }
  }

  if (node->node_id!=LIB3DS_NO_PARENT) {
    for (n=file->nodes; n!=0; n=p) {
      p=n->next;
      if (n->parent_id==node->node_id) {
        lib3ds_file_remove_node(file, n);
        lib3ds_file_insert_node(file, n);
      }
    }
  }
}


/*!
 * Remove a node from the a Lib3dsFile object.
 *
 * \param file The Lib3dsFile object to be modified.
 * \param node The Lib3dsNode object to be removed from file
 *
 * \return LIB3DS_TRUE on success, LIB3DS_FALSE if node is not found in file
 *
 * \ingroup file
 */
Lib3dsBool
lib3ds_file_remove_node(Lib3dsFile *file, Lib3dsNode *node)
{
  Lib3dsNode *p,*n;

  if (node->parent) {
    for (p=0,n=node->parent->childs; n; p=n,n=n->next) {
      if (n==node) {
        break;
      }
    }
    if (!n) {
      return(LIB3DS_FALSE);
    }
    
    if (!p) {
      node->parent->childs=n->next;
    }
    else {
      p->next=n->next;
    }
  }
  else {
    for (p=0,n=file->nodes; n; p=n,n=n->next) {
      if (n==node) {
        break;
      }
    }
    if (!n) {
      return(LIB3DS_FALSE);
    }
    
    if (!p) {
      file->nodes=n->next;
    }
    else {
      p->next=n->next;
    }
  }
  return(LIB3DS_TRUE);
}


/*!
 * Dump all node objects found in a Lib3dsFile object.
 *
 * Nodes are dumped recursively.
 *
 * \param file Lib3dsFile object to be dumped.
 *
 * \see lib3ds_node_dump
 *
 * \ingroup file
 */
void
lib3ds_file_dump_nodes(Lib3dsFile *file)
{
  Lib3dsNode *p;

  ASSERT(file);
  for (p=file->nodes; p!=0; p=p->next) {
    lib3ds_node_dump(p,1);
  }
}


/*!

\typedef Lib3dsFile
  \ingroup file
  \sa _Lib3dsFile

*/



/* Programming trick to force users to compile their source code against the
 * correct headers.  The symbol lib3ds_version1_3 will be defined iff the users
 * compile with the current version of <lib3ds/types.h>
 */

//extern int lib3ds_version1_3;
//static const int *lib3ds_version = &lib3ds_version1_3;
