added 3dengfx into the repo, probably not the correct version for this
[summerhack] / src / 3dengfx / libs / lib3ds / file.c
diff --git a/src/3dengfx/libs/lib3ds/file.c b/src/3dengfx/libs/lib3ds/file.c
new file mode 100644 (file)
index 0000000..33df4b9
--- /dev/null
@@ -0,0 +1,1917 @@
+/*
+ * 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>
+#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;
+*/