/*
 * 3DS API
 *
 * Includes the support needed to render 3D Studio r4 mesh files with 
 * OpenGL. Depends on the ObjGL2 class hierarchy.
 */

#include <io.h>
#include <stdio.h>
#include <malloc.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

#include "api3ds.h"
#include "macros.h"
#include <zlib.h>

#define API3DS_TEXT_OUT 1

bool Texture3DS::voodoo = false;
bool Texture3DS::lowtextures = false;
char* Loader3DS::buffer = 0;

const char* getlastext(const char* s) {
  const char* lastext = 0;
  char c;
  while(c=*s, c) {
    if(c=='.')
      lastext = s+1;
    s++;
  }
  return lastext;
}

template <class __type> inline Vector3<__type>
Normalize (const Vector3<__type>& V) {
  __type len = __type(1.0/sqrt(V.x*V.x + V.y*V.y + V.z*V.z));
  return Vector3<__type> (V.x*len, V.y*len, V.z*len);
}

template <class __type> inline Vector3<__type>
CrossProduct (const Vector3<__type>& u, const Vector3<__type>& v) {
  return Vector3<__type> (u.y*v.z - u.z*v.y,
                          v.x*u.z - u.x*v.z,
                          u.x*v.y - u.y*v.x);
}

template <class __type> inline __type
VectorLength(const Vector3<__type>& u) {
  return (__type)sqrt(u.x*u.x + u.y*u.y + u.z*u.z);
}

template <class __type> inline const __type&
Minimum(const __type& a, const __type& b) {
  return (a<b ? a : b);
}

void Texture3DS::GL ()
{
  if(Name()==0) {    
    glBindTexture(GL_TEXTURE_2D, 0);
    return;
  }
  if(texture==0)
    glGenTextures(1, &texture);

  glBindTexture(GL_TEXTURE_2D, texture);

  if(altered) {
    altered = false;
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, priority);
  }
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env_mode);
  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, env_color);

  if(!transferred) {
    Image img;

    String S = String("DATA\\") + Name();
    if (!img.Load(S())) img.Load(Name()());
/*
    String S(Name());
    FILE* file;
    file = fopen(S(), "rb");
    if(!file) {
      S = String("data\\")+S;
      file = fopen(S(), "rb");
      if(!file) {
       transferred = true;
       return;
      }
    } 
    fclose(file);
*/

    if(lowtextures) {
      img.Scale(img.width()>>1, img.height()>>1);
    }
    if(voodoo) {
      int nwidth = img.width()>256 ? 256 : img.width();
      int nheight = img.height()>256 ? 256 : img.height();
      if(nwidth!=img.width()||nheight!=img.height())
        img.Scale(nwidth, nheight);
    }

    switch(img.format()) {
      case OGL_IMAGE_RGB888:
        img.FlipVertical();
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8,
                     img.width(), img.height(), 0,
                     GL_RGB, GL_UNSIGNED_BYTE, img.data());
  
        if (API3DS_TEXT_OUT) cout << "texture " << Name() << " transferred" << endl;
        break;

      case OGL_IMAGE_RGBA8888:
        img.FlipVertical();
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8,
                     img.width(), img.height(), 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, img.data());
  

        if (API3DS_TEXT_OUT) cout << "texture " << Name() << " transferred" << endl;
        break;

      default:
        if (API3DS_TEXT_OUT) cout << "Bad image type " << img.format() << endl;
        break;
    }
    transferred = true;
  }
}

void Textures3DS::GL () {
  __entity_list.rewind();
  for(int i=__entity_list.size(); i; i--) {
    (*__entity_list)->GL();
    ++__entity_list;
  }
}

Texture3DS* Textures3DS::GetOrCreate (const char* s) {
  Texture3DS* p = Get(s);
  if(!p) {
    p = new Texture3DS;
    p->Name(s);
    p->MinFilter(GL_LINEAR);
    p->MagFilter(GL_LINEAR);

    Add(p);
  }
  return p;
}

Material3DS::Material3DS () {
  Reset();
}

void Material3DS::Reset () {
  ambient[0] = ambient[1] = ambient[2] = 0.0F; ambient[3] = 1.0F;
  diffuse[0] = diffuse[1] = diffuse[2] = 1.0F; diffuse[3] = 1.0F;
  specular[0] = specular[1] = specular[2] = 0.0F; specular[3] = 1.0F;

  shininess_percent = 0; 
  shininess_strenght_percent = 0;
  transparency_percent = 0;
  transparency_falloff_percent = 0;
  self_illumination_percent = 0;
  reflect_blur_percent = 0;

  texture1 = 0;
  texture2 = 0;
  opacity_map = 0;
  bump_map = 0;
  specular_map = 0;
  shininess_map = 0;
  self_illum_map = 0;
  reflection_map = 0;
}

void Material3DS::GL(GLenum face = GL_FRONT_AND_BACK)
{
  diffuse[3] = 0.6F; // (GLfloat)transparency_percent/100.0
  glMaterialfv(face, GL_AMBIENT, ambient);
  glMaterialfv(face, GL_DIFFUSE, diffuse);
  glMaterialfv(face, GL_SPECULAR, specular);
//  glMaterialf(face, GL_SHININESS, (GLfloat) shininess_percent);

  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);

  if(texture1==0) {
    glDisable(GL_TEXTURE_2D);
  }
  else {
    glEnable(GL_TEXTURE_2D);
    texture1->GL();
  }
}

void Material3DS::Ambient (GLfloat r, GLfloat g, GLfloat b) {
  SetVector4(ambient, r, g, b, 1.0F);
}
void Material3DS::Diffuse (GLfloat r, GLfloat g, GLfloat b) {
  SetVector4(diffuse, r, g, b, 1.0F);
}
void Material3DS::Specular (GLfloat r, GLfloat g, GLfloat b) {
  SetVector4(specular, r, g, b, 1.0F);
}
void Material3DS::Ambient (GLfloat* v) {
  SetVector4(ambient, *v, *(v+1), *(v+2), 1.0F);
}
void Material3DS::Diffuse (GLfloat* v) {
  SetVector4(diffuse, *v, *(v+1), *(v+2), 1.0F);
}
void Material3DS::Specular (GLfloat* v) {
  SetVector4(specular, *v, *(v+1), *(v+2), 1.0F);
}
void Material3DS::Ambient (GLubyte r, GLubyte g, GLubyte b) {
  SetVector4(ambient, r, g, b, 255);
}
void Material3DS::Diffuse (GLubyte r, GLubyte g, GLubyte b) {
  SetVector4(diffuse, r, g, b, 255);
}
void Material3DS::Specular (GLubyte r, GLubyte g, GLubyte b) {
  SetVector4(specular, r, g, b, 255);
}
void Material3DS::Ambient (GLubyte* v) {
  SetVector4(ambient, *v, *(v+1), *(v+2), 255);
}
void Material3DS::Diffuse (GLubyte* v) {
  SetVector4(diffuse, *v, *(v+1), *(v+2), 255);
}
void Material3DS::Specular (GLubyte* v) {
  SetVector4(specular, *v, *(v+1), *(v+2), 255);
}

void Material3DS::Shininess (GLint i) {
  shininess_percent = i;
}
void Material3DS::ShininessStrenght (GLint i) { 
  shininess_strenght_percent = i;
}
void Material3DS::Transparency (GLint i) {
  transparency_percent = i;
}
void Material3DS::TransparencyFalloff (GLint i) {
  transparency_falloff_percent = i;
}
void Material3DS::SelfIllumination (GLint i) {
  self_illumination_percent = i;
}
void Material3DS::ReflectBlur (GLint i) {
  reflect_blur_percent = i;
}

void Material3DS::Texture1 (Texture3DS* t) {
  texture1 = t;
}
void Material3DS::Texture2 (Texture3DS* t) {
  texture2 = t;
}
void Material3DS::OpacityMap (Texture3DS* t) {
  opacity_map = t;
}
void Material3DS::BumpMap (Texture3DS* t) {
  bump_map = t;
}
void Material3DS::SpecularMap (Texture3DS* t) {
  specular_map = t;
}
void Material3DS::ShininessMap (Texture3DS* t) {
  shininess_map = t;
}
void Material3DS::SelfIlluminationMap (Texture3DS* t) {
  self_illum_map = t;
}
void Material3DS::ReflectionMap (Texture3DS* t) {
  reflection_map = t;
}

const GLfloat* Material3DS::Ambient () {
  return ambient;
}
const GLfloat* Material3DS::Diffuse () {
  return diffuse;
}
const GLfloat* Material3DS::Specular () {
  return specular;
}
GLint Material3DS::Shininess () {
  return shininess_percent;
}
GLint Material3DS::ShininessStrenght () { 
  return shininess_strenght_percent;
}
GLint Material3DS::Transparency () {
  return transparency_percent;
}
GLint Material3DS::TransparencyFalloff () {
  return transparency_falloff_percent;
}
GLint Material3DS::SelfIllumination () {
  return self_illumination_percent;
}
GLint Material3DS::ReflectBlur () {
  return reflect_blur_percent;
}
Texture3DS* Material3DS::Texture1 () {
  return texture1;
}
Texture3DS* Material3DS::Texture2 () {
  return texture2;
}
Texture3DS* Material3DS::OpacityMap () {
  return opacity_map;
}
Texture3DS* Material3DS::BumpMap () {
  return bump_map;
}
Texture3DS* Material3DS::SpecularMap () {
  return specular_map;
}
Texture3DS* Material3DS::ShininessMap () {
  return shininess_map;
}
Texture3DS* Material3DS::SelfIlluminationMap () {
  return self_illum_map;
}
Texture3DS* Material3DS::ReflectionMap () {
  return reflection_map;
}


OmniLight3DS::OmniLight3DS () {
  Reset();
}

void OmniLight3DS::Reset ()
{
  Diffuse(0.0F, 0.0F, 0.0F);
  Position(0.0F, 0.0F, 0.0F);
  constant_attenuation = 1.0F;
  linear_attenuation = 0.0F;
  quadratic_attenuation = 0.0F;
}

void OmniLight3DS::GL (GLenum light, double time = 0.0)
{
  GLfloat a[4];
  a[3] = 1.0F;
  Vector3f v = diffuse.Val(time);
  v.Unpack(a);
  glLightfv(light, GL_DIFFUSE, a);
  v = position.Val(time);
  v.Unpack(a);
  glLightfv(light, GL_POSITION, a);

  a[0] = a[1] = a[2] = 0.0F;
  glLightfv(light, GL_SPECULAR, a);
  glLightfv(light, GL_AMBIENT, a);
  glLightf(light, GL_CONSTANT_ATTENUATION, constant_attenuation);
  glLightf(light, GL_LINEAR_ATTENUATION, linear_attenuation);
  glLightf(light, GL_QUADRATIC_ATTENUATION, quadratic_attenuation);
}

void OmniLight3DS::Diffuse (GLfloat r, GLfloat g, GLfloat b) {
  diffuse(Vector3f(r, g, b));
}
void OmniLight3DS::Diffuse (GLubyte r, GLubyte g, GLubyte b) {
  diffuse(Vector3f(UBYTE_TO_FLOAT(r), UBYTE_TO_FLOAT(g), UBYTE_TO_FLOAT(b)));
}
void OmniLight3DS::Diffuse (const Vector3f& V) {
  diffuse(V);
}
void OmniLight3DS::Diffuse (Dynamic<Vector3f, GLfloat> *ptr, bool strong = false) {
  diffuse(ptr, strong);
}

void OmniLight3DS::Position (GLfloat x, GLfloat y, GLfloat z) {
  position(Vector3f(x, y, z));
}
void OmniLight3DS::Position (const Vector3f& V) {
  position(V);
}
void OmniLight3DS::Position (Dynamic<Vector3f, GLfloat> *ptr, bool strong = false) {
  position(ptr, strong);
}

void OmniLight3DS::ConstantAttenuation (GLfloat f) {
  constant_attenuation = f;
}
void OmniLight3DS::LinearAttenuation (GLfloat f) {
  linear_attenuation = f;
}
void OmniLight3DS::QuadraticAttenuation (GLfloat f) {
  quadratic_attenuation = f;
}

Vector3f OmniLight3DS::Position (double time = 0.0) {
 return position.Val(time);
}
Vector3f OmniLight3DS::Diffuse (double time = 0.0) {
 return diffuse.Val(time);
}
GLfloat OmniLight3DS::ConstantAttenuation () {
  return constant_attenuation;
}
GLfloat OmniLight3DS::LinearAttenuation () {
  return linear_attenuation;
}
GLfloat OmniLight3DS::QuadraticAttenuation () {
  return quadratic_attenuation;
}

Object3DS::~Object3DS () {
  delete[] vertices;
  facegroups.rewind();
  for(int i=facegroups.size(); i; i--) {
    delete (*facegroups).indices;
    ++facegroups;
  }
}

BoundingBox BoundVertices (int n, Vertex3DS* p) {
  BoundingBox B;
  GLfloat mx, my, mz, Mx, My, Mz, a1, a2;
  mx = Mx = p->x;
  my = My = p->y;
  mz = Mz = p->z;
  Vertex3DS* p1 = p+1;

  for(int i=n-1; i; i--, p++, p1++)
  {
    a1 = p->x;
    a2 = p1->x;
    if(a1<=a2) {
      if(a1<mx) mx=a1;
      if(a2>Mx) Mx=a2;
    }
    else {
      if(a2<mx) mx=a2;
      if(a1>Mx) Mx=a1;
    }

    a1 = p->y;
    a2 = p1->y;
    if(a1<=a2) {
      if(a1<my) my=a1;
      if(a2>My) My=a2;
    }
    else {
      if(a2<my) my=a2;
      if(a1>My) My=a1;
    }

    a1 = p->z;
    a2 = p1->z;
    if(a1<=a2) {
      if(a1<mz) mz=a1;
      if(a2>Mz) Mz=a2;
    }
    else {
      if(a2<mz) mz=a2;
      if(a1>Mz) Mz=a1;
    }
  }
  B.Corner1(mx, my, mz);
  B.Corner2(Mx, My, Mz);
  return B;
}
/*
void Object3DS::Rays (Vector3f C, Vector3f S, GLfloat I) {
  glDisable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_ZERO);
  glDisable(GL_CULL_FACE);
  glBlendFunc(GL_ONE, GL_ONE);
  glEnable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);

  Vector3f V1, V2, V3, V4;

  glBegin(GL_QUADS);
  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  for(int z=0; z<numedges; z++, p++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;
    V1 = Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z);
    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
    if((a1*a2)<0.0) {
      V1 = Vector3f(v1->x-S.x, v1->y-S.y, v1->z-S.z);
      V2 = Vector3f(v2->x-S.x, v2->y-S.y, v2->z-S.z);
      GLfloat len1 = VectorLength(V1);
      GLfloat len2 = VectorLength(V2);
      GLfloat r1 = 1.0/len1;
      GLfloat r2 = 1.0/len2;
      a1*=r1;
      a2*=r2;
      GLfloat obrysovost = Minimum(fabs(a1), fabs(a2));
#define PI2 (PI/2)
      obrysovost=sin(sin(sin(sin(obrysovost*PI2)*PI2)*PI2)*PI2);



      GLfloat i1 = (1.0-len1/I)*obrysovost;
      GLfloat i2 = (1.0-len2/I)*obrysovost;
      if(i1<0.0||i2<0.0) continue;
      len1 = I*r1;
      len2 = I*r2;
      V1 *= len1;
      V2 *= len2;
      V1 += S;
      V2 += S;
      glColor3f(i1, i1, i1);
      glVertex3f(v1->x, v1->y, v1->z);
      glColor3f(i2, i2, i2);
      glVertex3f(v2->x, v2->y, v2->z);    
      glColor3f(0.0F, 0.0F, 0.0F);
      glVertex3f(V2.x, V2.y, V2.z);
      glVertex3f(V1.x, V1.y, V1.z);    
    }
  }

  glEnd();

  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}
*/

void Object3DS::Rays (Vector3f C, Vector3f S, GLfloat I, GLenum sfactor, GLenum dfactor) {
  glDisable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_ZERO);
  glDisable(GL_CULL_FACE);
  glBlendFunc(sfactor, dfactor);
  glEnable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);

  Vector3f V1, V2, V3, V4;

  glBegin(GL_QUADS);
  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  for(int z=0; z<numedges; z++, p++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;

    V1 = Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z);

    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
    if((a1*a2)<0.0) {

      V2 = Vector3f(v2->x-C.x, v2->y-C.y, v2->z-C.z);
      V3 = Normalize((V1+V2)*0.5F);
      V4 = Normalize(C-S);
      GLfloat d34= Dot(V3, V4);

      V1 = Vector3f(v1->x-S.x, v1->y-S.y, v1->z-S.z);
      V2 = Vector3f(v2->x-S.x, v2->y-S.y, v2->z-S.z);
      GLfloat len1 = VectorLength(V1);
      GLfloat len2 = VectorLength(V2);
      GLfloat r1 = 1.0/len1;
      GLfloat r2 = 1.0/len2;
      a1*=r1;
      a2*=r2;
#define PI2 (PI/2)

      GLfloat obrysovost = Minimum(fabs(a1), fabs(a2));
      obrysovost=sin(sin(sin(sin(obrysovost*PI2)*PI2)*PI2)*PI2);

      obrysovost*=sin(sin(sin(sin(sin(sin(cos(fabs(d34)*PI2)*PI2)*PI2)*PI2)*PI2)*PI2)*PI2);


      GLfloat i1 = (1.0-len1/I)*obrysovost;
      GLfloat i2 = (1.0-len2/I)*obrysovost;
      if(i1<0.0||i2<0.0) continue;
      len1 = I*r1;
      len2 = I*r2;
      V1 *= len1;
      V2 *= len2;
      V1 += S;
      V2 += S;
      i1*=0.3;
      i2*=0.3;
      glColor3f(i1, i1, i1);
      glVertex3f(v1->x, v1->y, v1->z);
      glColor3f(i2, i2, i2);
      glVertex3f(v2->x, v2->y, v2->z);    
      glColor3f(0.0F, 0.0F, 0.0F);
      glVertex3f(V2.x, V2.y, V2.z);
      glVertex3f(V1.x, V1.y, V1.z);    
    }
  }

  glEnd();

  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}

void Object3DS::ShadowVolume (Vector3f S, Vector3f C, GLfloat I, int j) {
  glDisable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glDepthMask(GL_ZERO);
//  glEnable(GL_CULL_FACE);
  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);

  glBlendFunc(GL_ONE, GL_ONE);
  glEnable(GL_BLEND);

  float cler=0.0;
  glDisable(GL_TEXTURE_2D);

  Vector3f V1, V2, V3;

  glBegin(GL_QUADS);
  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  for(int z=0; z<numedges; z++, p++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;

    V1 = Vector3f(v1->x-S.x, v1->y-S.y, v1->z-S.z);
    V3 = Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z);

    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
    Vector3f n1=Vector3f( p->N1_x+p->N2_x, p->N1_y+p->N2_y, p->N1_z+p->N2_z)*0.5F;
    Vector3f n2=Vector3f( v1->x-v2->x,  v1->y-v2->y,  v1->z-v2->z);
    Vector3f n3=Cross(n2,V1);
    if ( Dot(n3,n1)<0.0 ) n3=-n3;

    GLfloat a3= (V3.x*n3.x + V3.y*n3.y + V3.z*n3.z)*j;

    if (a3>0.0)
    if((a1*a2)<0.0) {

      V2 = Vector3f(v2->x-S.x, v2->y-S.y, v2->z-S.z);

 
      float len1 = (I/sqrt(V1.x*V1.x + V1.y*V1.y + V1.z*V1.z));
      V1.x=V1.x*len1 + v1->x;
      V1.y=V1.y*len1 + v1->y;
      V1.z=V1.z*len1 + v1->z;

      float len2 = (I/sqrt(V2.x*V2.x + V2.y*V2.y + V2.z*V2.z));
      V2.x=V2.x*len2 + v2->x;
      V2.y=V2.y*len2 + v2->y;
      V2.z=V2.z*len2 + v2->z;



      glColor3f(cler,cler,cler);
      glVertex3f(v1->x, v1->y, v1->z);
      glVertex3f(v2->x, v2->y, v2->z);    
      glVertex3f(V2.x, V2.y, V2.z);
      glVertex3f(V1.x, V1.y, V1.z);    
    }
  }

  glEnd();

  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}


void Object3DS::ShortLine ( Vector3f C, GLfloat W, GLfloat r, GLfloat g, GLfloat b) {
  glDisable(GL_LIGHTING);
  glDisable(GL_CULL_FACE);
//  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
  glLineWidth(W);
  Vector3f V1;

  glBegin(GL_LINES);
  glColor3f(r,g,b);

  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  float total=0.0;
  for(int z=0; z<numedges; z++, p++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;
    V1 = Normalize( Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z) );
    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
//    if((a1>=0.0)&&(a2>=0.0))
//    if((a1<=0.0)&&(a2<=0.0))
      {
      glVertex3f(v1->x, v1->y, v1->z);
      glVertex3f(v2->x, v2->y, v2->z);    
      }
    //cout << "Vertex " << z << ": " << p->a << "," << p->b << endl;
    total++;
    }
  glEnd();
  //cout << "Shortline total: " << total << endl;
  glEnable(GL_CULL_FACE);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}


void DrawLongLine(float x1,float y1, float x2, float y2)
{
float xl,yl;
float xr,yr;
float xu,yu;
float xd,yd;
int   l,r,u,d;
float a;

l=r=u=d=0;
float dx=x2-x1;
float dy=y2-y1;


if (dx!=0) 
  { 
  a=( 1.0-x2)/dx;
  xr=1.0;
  yr=y2+a*dy;
  if ((yr<1.0)&&(yr>=-1.0)) r=1;

  a=(-1.0-x2)/dx;
  xl=-1.0;
  yl=y2+a*dy;
  if ((yl<=1.0)&&(yl>-1.0)) l=1;
  }

if (dy!=0) 
  { 
  a=( 1.0-y2)/dy;
  yu=1.0;
  xu=x2+a*dx;
  if ((xu<=1.0)&&(xu>-1.0)) u=1;

  a=(-1.0-y2)/dy;
  yd=-1.0;
  xd=x2+a*dx;
  if ((xd<1.0)&&(xd>=-1.0)) d=1;
  }

  if (l) glVertex2f(xl,yl);
  if (r) glVertex2f(xr,yr);
  if (u) glVertex2f(xu,yu);
  if (d) glVertex2f(xd,yd);
}


void Object3DS::LongLine ( Vector3f ang, Vector3f pos, GLfloat scale, GLfloat w, GLfloat r, GLfloat g, GLfloat b)
{
  glLineWidth(w);

  GLmatrix m=Euler2Matrix(ang.x,ang.y,ang.z);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();


  glBegin(GL_LINES);
  glColor3f(r,g,b);

  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  Vector2f sc1,sc2;
  p = edges; 
  for(int z=0; z<numedges; z++, p++) {
      v1 = evertices + p->a;
      v2 = evertices + p->b;

      Vector4f t1(v1->x,v1->y,v1->z,1.0F); 
      Vector4f t2(v2->x,v2->y,v2->z,1.0F); 

      m.MultVertex4f(t1.x,t1.y,t1.z,t1.w);
      m.MultVertex4f(t2.x,t2.y,t2.z,t2.w);

      sc1= Vector2f(pos.x+ scale*t1.x/ (t1.z+pos.z), pos.y+ scale*t1.y/ (t1.z+pos.z));
      sc2= Vector2f(pos.x+ scale*t2.x/ (t2.z+pos.z), pos.y+ scale*t2.y/ (t2.z+pos.z));

    DrawLongLine(sc1.x,sc1.y,sc2.x,sc2.y);
    }
  glEnd();

  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);

}


void Object3DS::ShortLine ( Vector3f ang, Vector3f pos, GLfloat scale, GLfloat w, GLfloat r, GLfloat g, GLfloat b)
{
  glLineWidth(w);

  GLmatrix m=Euler2Matrix(ang.x,ang.y,ang.z);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();


  glBegin(GL_LINES);
  glColor3f(r,g,b);

  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  Vector2f sc1,sc2;
  p = edges; 
  for(int z=0; z<numedges; z++, p++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;


      Vector4f t1(v1->x,v1->y,v1->z,1.0F); 
      Vector4f t2(v2->x,v2->y,v2->z,1.0F); 

      m.MultVertex4f(t1.x,t1.y,t1.z,t1.w);
      m.MultVertex4f(t2.x,t2.y,t2.z,t2.w);

      sc1= Vector2f(pos.x+ scale*t1.x/ (t1.z+pos.z), pos.y+ scale*t1.y/ (t1.z+pos.z));
      sc2= Vector2f(pos.x+ scale*t2.x/ (t2.z+pos.z), pos.y+ scale*t2.y/ (t2.z+pos.z));
      glVertex2f(sc1.x, sc1.y);
      glVertex2f(sc2.x, sc2.y);    

    }
  glEnd();

  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);

}


void Object3DS::Outline ( Vector3f C, GLfloat W, GLfloat r, GLfloat g, GLfloat b) {
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
//  glDepthMask(GL_ZERO);
  glDisable(GL_CULL_FACE);
//  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
  glLineWidth(W);
  Vector3f V1;

  glBegin(GL_LINES);
  glColor3f(r,g,b);

  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  float total=0.0;
  float outl=0.0;
  float other=0.0;

  for(int z=0; z<numedges; z++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;
    V1 = Normalize( Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z) );
    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
    if((a1*a2)<0.0)
      {
//      GLfloat obr = Minimum(fabs(a1), fabs(a2));
//      obr=sin(sin(sin(sin(obr*PI2)*PI2)*PI2)*PI2);
//      glColor3f(obr,obr,obr);
      glVertex3f(v1->x, v1->y, v1->z);
      glVertex3f(v2->x, v2->y, v2->z);    
      outl++;
      } else other++;
    total++;
    p++;
    }
  glEnd();
  //cout << "Longline total: " << total << "  good: " << outl << "  bad: " << other << endl;
  //if (total>numedges) cout << "error 1" << endl;
  //if (total!=outl+other) cout << "error 2" << endl;
  glEnable(GL_CULL_FACE);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}


void Object3DS::Inline ( Vector3f C, GLfloat W, GLfloat r, GLfloat g, GLfloat b) {
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
//  glDepthMask(GL_ZERO);
  glDisable(GL_CULL_FACE);
//  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
  glLineWidth(W);
  Vector3f V1;

  glBegin(GL_LINES);
  glColor3f(r,g,b);

  Edge3DS* p = edges;
  Vertex3DS* v1;
  Vertex3DS* v2;
  p = edges; 
  float total=0.0;
  float outl=0.0;
  float other=0.0;

  for(int z=0; z<numedges; z++) {
    v1 = evertices + p->a;
    v2 = evertices + p->b;
    V1 = Normalize( Vector3f(v1->x-C.x, v1->y-C.y, v1->z-C.z) );
    GLfloat a1 = V1.x*p->N1_x + V1.y*p->N1_y + V1.z*p->N1_z;
    GLfloat a2 = V1.x*p->N2_x + V1.y*p->N2_y + V1.z*p->N2_z;
    if((a1*a2)>=0.0)
      {
//      GLfloat obr = Minimum(fabs(a1), fabs(a2));
//      obr=sin(sin(sin(sin(obr*PI2)*PI2)*PI2)*PI2);
//      glColor3f(obr,obr,obr);
      glVertex3f(v1->x, v1->y, v1->z);
      glVertex3f(v2->x, v2->y, v2->z);    
      outl++;
      } else other++;
    total++;
    p++;
    }
  glEnd();
  if (total>numedges) cout << "error 3" << endl;
  if (total!=outl+other) cout << "error 4" << endl;
  glEnable(GL_CULL_FACE);
  glDepthMask(GL_ONE);
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
}

void Object3DS::Render (double time = 0.0) {
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  GLmatrix M = lcs.Val(time);
  glMultMatrixf(M());

  if(!(bbox.IsVisible())) {
    glPopMatrix();
    return;
  }

  glInterleavedArrays(GL_T4F_C4F_N3F_V4F, 0, (void*)vertices);

  glLockArraysEXT(0, numvertices);

  facegroups.rewind();
  for(int i=facegroups.size(); i; i--) {
    FaceGroup3DS& fg = *facegroups;
    Material3DS* mat = fg.material;
    mat->GL();
    switch(mat->Transparency()) {
      case 50:
        glDepthMask(GL_ZERO);
        glDisable(GL_CULL_FACE);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        break;
      case 49:
      case 51:
        glDepthMask(GL_ZERO);
        glDisable(GL_CULL_FACE);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        break;
      default:
        glDepthMask(GL_ONE);
        glEnable(GL_CULL_FACE);
        glDisable(GL_BLEND);
        break;
    }
    glDrawElements(GL_TRIANGLES, 3*(fg.numfaces), GL_UNSIGNED_INT, (void*)(fg.indices));
    ++facegroups;
  }

  glUnlockArraysEXT();

  glDisable(GL_CULL_FACE);
  glDisable(GL_BLEND);
  glDepthMask(GL_ONE);

  glPopMatrix();
}

void Object3DS::Vertices (int n, Vertex3DS* p) {
  numvertices = n;
  vertices = p;
  bbox = BoundVertices(n, p);
}
void Object3DS::AddFaceGroup (Material3DS* mat, int n, GLuint* ind) {
  facegroups.push_back(FaceGroup3DS(mat, n, ind));
}
void Object3DS::EdgeVertices (int n, Vertex3DS* p) {
  numevertices = n;
  evertices = p;
}
void Object3DS::Edges (int n, Edge3DS* p) {
  numedges = n;
  edges = p;
}
void Object3DS::Facenormals (int n, Vector3f* p) {
  numfaces = n;
  facenormals = p;
}
void Object3DS::CoordinateSystem (const GLmatrix& V) {
  lcs(V);
}
void Object3DS::CoordinateSystem (Dynamic<GLmatrix, GLfloat> *ptr, bool strong = false) {
  lcs(ptr, strong);
}

Scene3DS::Scene3DS () {
  allframes = 0;

  Material3DS* default_material = new Material3DS;
  default_material->Name("____DEFAULT_MATERIAL");
  Add(default_material);

  return;
}
Scene3DS::~Scene3DS () {
  lights.DeleteEntities();
  materials.DeleteEntities();
  textures.DeleteEntities();
  cameras.DeleteEntities();
  objects.DeleteEntities();
}
void Scene3DS::Render (double time = 0.0) {
  lights.GL(time);
  objects.Render(time);
}
void Scene3DS::Add (Light* p) {
  lights+=p;
}
void Scene3DS::Add (Texture3DS* p) {
  textures+=p;
}
void Scene3DS::Add (Material3DS* p) {
  materials+=p;
}
void Scene3DS::Add (CameraTarget3DS* p) {
  cameras+=p;
}
void Scene3DS::Add (RenderableEntity* p) {
  objects+=p;
}
void Scene3DS::Add (const RenderableEntities & os) {
  objects.Add(os);
}
Light* Scene3DS::GetLight (String s) {
  return lights.Get(s);
}
Texture3DS* Scene3DS::GetTexture (String s) {
  return textures.Get(s);
}
Material3DS* Scene3DS::GetMaterial (String s) {
  return materials.Get(s);
}
CameraTarget3DS* Scene3DS::GetCamera (String s) {
  return cameras.Get(s);
}
RenderableEntity* Scene3DS::GetObject (String s) {
  return objects.Get(s);
}

Lights Scene3DS::GetLights () {
  return lights;
}
Textures3DS Scene3DS::GetTextures () {
  return textures;
}
Materials3DS Scene3DS::GetMaterials () {
  return materials;
}
Cameras3DS Scene3DS::GetCameras () {
  return cameras;
}
RenderableEntities Scene3DS::GetObjects () {
  return objects;
}

void Scene3DS::Ambient (GLfloat r, GLfloat g, GLfloat b, GLfloat a=1.0) {
  SetVector4(ambient, r, g, b, a);
}

char* Loader3DS::find_chunk(char *father, unsigned short chunk_type, char *act)
{
  char *chunk_end = father + getlong(father+2);
  while((act<chunk_end) && (getshort(act)!=chunk_type))
    act += getlong(act+2);
  if(act<chunk_end)
    return act;
  else
    return 0;
}

List<chunk3ds> Loader3DS::subchunks(char *father, char *act)
{
  List<chunk3ds> l;
  chunk3ds c;
  char *chunk_end = father + getlong(father+2);
  while(act<chunk_end)
  {
    c.type=getshort(act);
    c.ptr=act;
    l.push_back(c);
    act+=getlong(act+2);
  }
  return l;
}

List<chunk3ds> Loader3DS::subchunks_by_type(char *father, unsigned short chunk_type, char *act)
{
  List<chunk3ds> l;
  chunk3ds c;
  char *chunk_end = father + getlong(father+2);
  while(act<chunk_end)
  {
    if(getshort(act) == chunk_type)
    {
      c.type = getshort(act);
      c.ptr = act;
      l.push_back(c);
    }
    act+=getlong(act+2);
  }
  return l;
}

void Loader3DS::tcb_info(unsigned short flag)
{
//  if (API3DS_TEXT_OUT) cout << flag << " ";
  if(flag&1)
    if (API3DS_TEXT_OUT) cout << "tension ";
  if(flag&2)
    if (API3DS_TEXT_OUT) cout << "continuity ";
  if(flag&4)
    if (API3DS_TEXT_OUT) cout << "bias ";
  if(flag&8)
    if (API3DS_TEXT_OUT) cout << "ease-to ";
  if(flag&16)
    if (API3DS_TEXT_OUT) cout << "ease-from ";
}

char* Loader3DS::fill_tcbee(unsigned short flag, char *p, TCBEE *tcb)
{
  tcb->tension = 0.0;
  tcb->continuity = 0.0;
  tcb->bias = 0.0;
  tcb->easeto = 0.0;
  tcb->easefrom = 0.0;

  if(flag&1) {
    tcb->tension = getfloat(p);
    p+=4;
  }
  if(flag&2) {
    tcb->continuity = getfloat(p);
    p+=4;
  }
  if(flag&4) {
    tcb->bias = getfloat(p);
    p+=4;
  }
  if(flag&8) {
    tcb->easeto = getfloat(p);
    p+=4;
  }
  if(flag&16) {
    tcb->easefrom = getfloat(p);
    p+=4;
  }
  return p;
}


void Loader3DS::track_type_info(unsigned short flag)
{
  switch(flag&3) {
    case 0:
      if (API3DS_TEXT_OUT) cout << "single";
      break;
    case 2:
      if (API3DS_TEXT_OUT) cout << "repeat";
      break;
    case 3:
      if (API3DS_TEXT_OUT) cout << "loop";
      break;
  }
  switch((flag&52)>>3) {
    case 1:
      if (API3DS_TEXT_OUT) cout << ", lock X";
      break;
    case 2:
      if (API3DS_TEXT_OUT) cout << ", lock Y";
      break;
    case 4:
      if (API3DS_TEXT_OUT) cout << ", lock Z";
      break;
  }
  switch((flag&896)>>7) {
    case 1:
      if (API3DS_TEXT_OUT) cout << ", unlink X";
      break;
    case 2:
      if (API3DS_TEXT_OUT) cout << ", unlink Y";
      break;
    case 4:
      if (API3DS_TEXT_OUT) cout << ", unlink Z";
      break;
  }
}

Scene3DS* Loader3DS::Load(const char *inputfile, Textures3DS* texturelib = 0)
{
  Scene3DS* scene = 0;
  bool take_care_of_buf_alloc = buffer ? false : true;

  try {

  if(take_care_of_buf_alloc)
    buffer = new char[BUFFER_SIZE_3DS];

  gzFile file;
  file = gzopen(inputfile,"rb");
  if(file == 0) {
    if (API3DS_TEXT_OUT) cout << "3DS Loader: Error: Can't open """ << inputfile << """." << endl; 
    throw 1;
  }
  gzread(file, buffer, BUFFER_SIZE_3DS);
  gzclose(file);
/*
  int f = open(inputfile, O_BINARY|O_RDONLY);
  if (f==-1) {
    if (API3DS_TEXT_OUT) cout << "3DS Loader: Error: File not found." << endl;
    throw 1;
  }

  struct stat st;
  fstat(f,&st);
  buffer = new char[st.st_size];
  read(f,buffer,st.st_size);
  close(f);
*/
  if (API3DS_TEXT_OUT) cout << "3DS Loader: Processing \""<< inputfile << "\":" << endl;
  if (getshort(buffer)!=MAIN_3DS_CHUNK)
  {
    if (API3DS_TEXT_OUT) cout << "3DS Loader: Error: Invalid inputfile." << endl;
    throw 1;
  }

  // start processing
  char *pmesh = buffer;

  scene = new Scene3DS;
  scene->Name(inputfile);

  // find the editor chunk
  char *peditor = find_chunk(pmesh, EDITOR_CHUNK, pmesh+6);

  // look for ambient color
  char *p = find_chunk(peditor, AMBIENT_COLOR, peditor+6);
  if(p!=0)
  {
    p = find_chunk(p, RGBfloat, p+6);
    if(p!=0)
    {
      p+=6;
      if (API3DS_TEXT_OUT) cout<<" Ambient color: ("
         <<(int)(256*getfloat(p))<<", "
         <<(int)(256*getfloat(p+4))<<", "
         <<(int)(256*getfloat(p+8))<< ")" << endl;
      scene->Ambient(getfloat(p), getfloat(p+4), getfloat(p+8));
    } 
  }

  List<chunk3ds> pmats(subchunks_by_type(peditor, MATERIAL_BLOCK, peditor+6));
  if (API3DS_TEXT_OUT) cout << " Materials: " << endl;
  char *pmat;
  pmats.rewind();
  for(int nm=pmats.size(); nm; nm--)
  {

    pmat = (*pmats).ptr;
    p = find_chunk(pmat, MATERIAL_NAME, pmat+6);
    if (API3DS_TEXT_OUT) cout << "  \"" << (const char *)(p+6) << "\":" << endl;

    Material3DS *mat = new Material3DS;
    mat->Name((const char *)(p+6));
    scene->Add(mat);

    p = find_chunk(pmat, MATERIAL_AMBIENT_COLOR, pmat+6);
    if(p!=0) 
    {
      GLubyte *rgbcolor = (GLubyte *)find_chunk(p, RGBbyte, p+6)+6;
      if (API3DS_TEXT_OUT) cout << "     Ambient color: ("
          << (int) *rgbcolor << ", "
          << (int) *(rgbcolor+1) << ", "
          << (int) *(rgbcolor+2) << ")" << endl;
      mat->Ambient(*rgbcolor, *(rgbcolor+1), *(rgbcolor+2));
    }
    p = find_chunk(pmat, MATERIAL_DIFFUSE_COLOR, pmat+6);
    if(p!=0) 
    {
      GLubyte *rgbcolor = (GLubyte *)find_chunk(p, RGBbyte, p+6)+6;
      if (API3DS_TEXT_OUT) cout << "     Diffuse color: ("
          << (int) *rgbcolor << ", "
          << (int) *(rgbcolor+1) << ", "
          << (int) *(rgbcolor+2) << ")" << endl;
      mat->Diffuse(*rgbcolor, *(rgbcolor+1), *(rgbcolor+2));
    }
    p = find_chunk(pmat, MATERIAL_SPECULAR_COLOR, pmat+6);
    if(p!=0) 
    {
      GLubyte *rgbcolor = (GLubyte *)find_chunk(p, RGBbyte, p+6)+6;
      if (API3DS_TEXT_OUT) cout << "     Specular color: ("
          << (int) *rgbcolor << ", "
          << (int) *(rgbcolor+1) << ", "
          << (int) *(rgbcolor+2) << ")" << endl;
      mat->Specular(*rgbcolor, *(rgbcolor+1), *(rgbcolor+2));
    }

    p = find_chunk(pmat, TRANSPARENCY_PERCENT, pmat+6);
    if(p!=0)
    {
      char *percent = find_chunk(p, PERCENTword, p+6)+6;
      if (API3DS_TEXT_OUT) cout << "     Transparency: " << getshort(percent) << "%" << endl;
      int xp = getshort(percent);
      mat->Transparency(xp);
    }

    p = find_chunk(pmat, MAP1, pmat+6);
    if(p!=0)
    {
      char *ptname = find_chunk(p, MAPPING_FILENAME, p+6)+6;
// *-> data directory access
//      char tm_p[30]="data\\";
//      strcat(tm_p,ptname);
//      strcpy(ptname,tm_p);

      if (API3DS_TEXT_OUT) cout << "     Diffuse map: \"" << (const char *)ptname << "\"" << endl;

      /*
       * An external texture library can be passed as a second argument
       * to Load(...)
       */
      Texture3DS* tex;
      if(texturelib) {
        tex = texturelib->GetOrCreate((const char*)ptname);
      }
      else {
        tex = new Texture3DS;
        tex->Name((const char *)ptname);
        tex->MinFilter(GL_LINEAR);
        tex->MagFilter(GL_LINEAR);

        scene->Add(tex);
      }
      mat->Texture1(tex);

    }

    ++pmats;
  }

  // list obj blocks within editor
  List<chunk3ds> pl(subchunks_by_type(peditor, OBJECT_BLOCK, peditor+6));
  pl.rewind();
  char *po, *pi, *ptri;

  RenderableEntities transparent_objects;
  bool istransparent;

  if (API3DS_TEXT_OUT) cout << " Object blocks:" << endl;

  const char *pname;
  for(int no=pl.size(); no; no--)
  {
    po = (*pl).ptr;
    ++pl;
    pi = po + 6;
    pname = (const char *)pi;
    if (API3DS_TEXT_OUT) cout << "  \"" << pname << "\" ";
    while(*pi) pi++; pi++;

    if(find_chunk(po, TRIANGULAR_MESH, pi))
    {
      if (API3DS_TEXT_OUT) cout << "A mesh. ";
      ptri = find_chunk(po, TRIANGULAR_MESH, pi);

      // find vertices
      int vertex_count = 0;
      Vector3DS *pvertices = 0;
      p = find_chunk(ptri, VERTICES, ptri+6);
      if(p!=0)
        {
          vertex_count = getshort(p+6);
          pvertices = (Vector3DS *)(p+8);
        }

      // find faces
      int face_count = 0;
      Face3DS *pfaces = 0;
      p = find_chunk(ptri, FACES, ptri+6);
      char *pfaceschunk = p;
      if(p!=0)
        {
          face_count = getshort(p+6);
          pfaces = (Face3DS *)(p+8);
        }

      // find mapping info
      int map_vertex_count = 0;
      Mapping3DS *pmapping = 0;
      p = find_chunk(ptri, MAPPING_COORDS, ptri+6);
      if(p!=0)
        {
          map_vertex_count = getshort(p+6);
          pmapping = (Mapping3DS *)(p+8);
        }

      if(pvertices && pfaces)
      {
        if (API3DS_TEXT_OUT) cout <<"("<<vertex_count<<" vertices, "<<face_count<<" faces"<<")";
        if(pmapping)
          if (API3DS_TEXT_OUT) cout << " mapped.";
        if (API3DS_TEXT_OUT) cout << endl;

        Object3DS *object = new Object3DS;
        object->Name(pname);
        istransparent = false;

        // for every i-th face smooths[i] is the smoothing groups info
        unsigned long *smooths = 0;
        if(find_chunk(pfaceschunk, SMOOTHINGS, (char *)(pfaces+face_count))) {
          smooths = (unsigned long *)(6+find_chunk(pfaceschunk, SMOOTHINGS, (char *)(pfaces+face_count)));
        }

        iface Iface[face_count];
        Vector3f P1, P2, P3;
        for(int z=0; z<face_count; z++) {
          Iface[z].a = pfaces[z].a;
          Iface[z].b = pfaces[z].b;
          Iface[z].c = pfaces[z].c;
          if(smooths)
            Iface[z].sgroup = smooths[z];
          else
            Iface[z].sgroup = 1;
          Vector3DS* pv;
          pv = pvertices + pfaces[z].a;
          P1 = Vector3f(pv->x, pv->y, pv->z);
          pv = pvertices + pfaces[z].b;
          P2 = Vector3f(pv->x, pv->y, pv->z);
          pv = pvertices + pfaces[z].c;
          P3 = Vector3f(pv->x, pv->y, pv->z);
          Iface[z].N = Normalize(CrossProduct(P2-P1, P3-P1));
          Iface[z].hasmaterial = false;
          Iface[z].ab_done = !(pfaces[z].flags&4);
          Iface[z].ac_done = !(pfaces[z].flags&1);
          Iface[z].bc_done = !(pfaces[z].flags&2);
//          Iface[z].ab_done = false;
//          Iface[z].ac_done = false;
//          Iface[z].bc_done = false;
        }

        Vertex3DS* vert = new Vertex3DS[vertex_count*2];

        for(int z=0; z<vertex_count; z++) {
          vert[z].x = pvertices[z].x;
          vert[z].y = pvertices[z].y;
          vert[z].z = pvertices[z].z;
          vert[z].w = 1.0F;
          vert[z].R = vert[z].G = vert[z].B = vert[z].A = 1.0F;
          vert[z].r = 0.0F;
          vert[z].q = 1.0F;
          if(pmapping) {
            vert[z].s = pmapping[z].u;
            vert[z].t = pmapping[z].v;
          }
          else {
            vert[z].s = 0.0F;
            vert[z].t = 0.0F;
          }

          vert[z].i = 0.0F;
          vert[z].j = 0.0F;
          vert[z].k = 0.0F;
        }

        /*
         * Here we create a copy of the vertices, which is suitable for
         * creating volumetric effects.
         */
        for(int z=0; z<vertex_count; z++) {
          vert[z+vertex_count] = vert[z];
        }
        object->EdgeVertices(vertex_count*2, vert);

        /*
         * We build an array of normals at each face, suitable for effects.
         */
        Vector3f* fnormals = new Vector3f[face_count];
        for(int z=0; z<face_count; z++) {
          fnormals[z] = Iface[z].N;
        }
        object->Facenormals(face_count, fnormals);

        /*
         * For each vertex we build a table of pointers to faces 
         * that share it.
         */

        shinfo sharings[vertex_count];
        for(int z=0; z<vertex_count; z++) {
          sharings[z].numfaces = 0;
          sharings[z].used = 0;
          sharings[z].splits = 0;
          sharings[z].p = 0;
          sharings[z].v = 0;
          sharings[z].iv = 0;
        }
          
        for(int z=0; z<face_count; z++) {
          sharings[Iface[z].a].numfaces++;
          sharings[Iface[z].b].numfaces++;
          sharings[Iface[z].c].numfaces++;
        }
        
        ftable fgroups[face_count*3];
        for(int z=0; z<(face_count*3); z++) {
          fgroups[z].group = 0;
        }
        ftable* fptr = fgroups;
        for(int z=0; z<vertex_count; z++) {
          sharings[z].p = fptr;
          fptr+=sharings[z].numfaces;
        }
        
        for(int z=0; z<face_count; z++) {
          (sharings[Iface[z].a].p + sharings[Iface[z].a].used)->fi = z;
          sharings[Iface[z].a].used++;
          (sharings[Iface[z].b].p + sharings[Iface[z].b].used)->fi = z;
          sharings[Iface[z].b].used++;
          (sharings[Iface[z].c].p + sharings[Iface[z].c].used)->fi = z;
          sharings[Iface[z].c].used++;
        }

        /*
         * Build an array of this objects polygon edges
         */
        Edge3DS* edges = new Edge3DS[face_count*3];
        int edge_count = 0;

        for(int z=0; z<face_count; z++) {
          GLuint aa = Iface[z].a;
          GLuint bb = Iface[z].b;
          GLuint cc = Iface[z].c;
          // AB edge
          if(!Iface[z].ab_done) {
            Iface[z].ab_done = true;
            edges[edge_count].a = aa;
            edges[edge_count].b = bb;
            edges[edge_count].N1_x = Iface[z].N.x;
            edges[edge_count].N1_y = Iface[z].N.y;
            edges[edge_count].N1_z = Iface[z].N.z;

            ftable* fp = sharings[aa].p;
            int d = 0;
            for(; d<sharings[aa].numfaces; d++, fp++) {
              if(z==fp->fi) continue;
              iface& face = Iface[fp->fi];
              if((face.a==aa&&face.b==bb)||(face.a==bb&&face.b==aa)) {
                face.ab_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.a==aa&&face.c==bb)||(face.a==bb&&face.c==aa)) {
                face.ac_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.b==aa&&face.c==bb)||(face.b==bb&&face.c==aa)) {
                face.bc_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
            }
            if(d==sharings[aa].numfaces) {
              edges[edge_count].N2_x = -Iface[z].N.x;
              edges[edge_count].N2_y = -Iface[z].N.y;
              edges[edge_count].N2_z = -Iface[z].N.z;
            }
            edge_count++;
          }
          // AC edge
          if(!Iface[z].ac_done) {
            Iface[z].ac_done = true;
            edges[edge_count].a = aa;
            edges[edge_count].b = cc;
            edges[edge_count].N1_x = Iface[z].N.x;
            edges[edge_count].N1_y = Iface[z].N.y;
            edges[edge_count].N1_z = Iface[z].N.z;
            ftable* fp = sharings[aa].p;
            int d = 0;
            for(; d<sharings[aa].numfaces; d++, fp++) {
              if(z==fp->fi) continue;
              iface& face = Iface[fp->fi];
              if((face.a==aa&&face.b==cc)||(face.a==cc&&face.b==aa)) {
                face.ab_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.a==aa&&face.c==cc)||(face.a==cc&&face.c==aa)) {
                face.ac_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.b==aa&&face.c==cc)||(face.b==cc&&face.c==aa)) {
                face.bc_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
            }
            if(d==sharings[aa].numfaces) {
              edges[edge_count].N2_x = -Iface[z].N.x;
              edges[edge_count].N2_y = -Iface[z].N.y;
              edges[edge_count].N2_z = -Iface[z].N.z;
            }
            edge_count++;
          }
          // BC edge
          if(!Iface[z].bc_done) {
            Iface[z].bc_done = true;
            edges[edge_count].a = bb;
            edges[edge_count].b = cc;
            edges[edge_count].N1_x = Iface[z].N.x;
            edges[edge_count].N1_y = Iface[z].N.y;
            edges[edge_count].N1_z = Iface[z].N.z;
            ftable* fp = sharings[bb].p;
            int d = 0;
            for(; d<sharings[bb].numfaces; d++, fp++) {
              if(z==fp->fi) continue;
              iface& face = Iface[fp->fi];
              if((face.a==bb&&face.b==cc)||(face.a==cc&&face.b==bb)) {
                face.ab_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.a==bb&&face.c==cc)||(face.a==cc&&face.c==bb)) {
                face.ac_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
              if((face.b==bb&&face.c==cc)||(face.b==cc&&face.c==bb)) {
                face.bc_done = true;
                edges[edge_count].N2_x = face.N.x;
                edges[edge_count].N2_y = face.N.y;
                edges[edge_count].N2_z = face.N.z;
                break;
              }
            }
            if(d==sharings[bb].numfaces) {
              edges[edge_count].N2_x = -Iface[z].N.x;
              edges[edge_count].N2_y = -Iface[z].N.y;
              edges[edge_count].N2_z = -Iface[z].N.z;
            }
            edge_count++;
          }
        }

        object->Edges(edge_count, edges);
        if (API3DS_TEXT_OUT) cout<< "         Edges: " << edge_count << endl;
        
        /* 
         * For each vertex we divide the faces sharing it into groups
         * according to the smoothing info. The algorithm used assumes
         * some "good" structure of smoothing data, and cannot handle
         * all the possible (although unprobable) cases.
         */
       
        int vertex_count2 = 0;
        if(smooths) {
          for(int z=0; z<vertex_count; z++) {
            GLuint g = 0;
            ftable* fp = sharings[z].p;
            /*
             * here we try to handle out the faces w/o smoothing info
             */
            for(int d=0; d<sharings[z].numfaces; d++) {
              if(Iface[fp->fi].sgroup==0) {
                g++;
                fp->group = g;
              }
              fp++;
            }
            while(true) {
              /*
               * and from now on just the nice behaving faces
               */
              int r = 0;
              fp = sharings[z].p;
              r = 0;
              for(; r<sharings[z].numfaces; r++) {
                if(!fp->group) break;
                fp++;
              }
              if(r==sharings[z].numfaces) break;
              g++;
              unsigned long cgroup = Iface[fp->fi].sgroup;
              for(; r<sharings[z].numfaces; r++) {
                if(!fp->group) 
                  if(Iface[fp->fi].sgroup&cgroup)
                    fp->group = g;
                fp++;
              }
            }
            sharings[z].splits = g;
            vertex_count2 += g;
          }
        }
        else {
          /*
           * This is for the case when the object is not smoothed at all.
           */
          for(int z=0; z<vertex_count; z++) {
            ftable* fp = sharings[z].p;
            for(int q=1; q<=sharings[z].numfaces; q++) {
              fp->group = q;
              fp++;
            }
            sharings[z].splits = sharings[z].numfaces;
            vertex_count2 += sharings[z].numfaces;
          }
        }

        Vertex3DS* vert2 = new Vertex3DS[vertex_count2];
        Vertex3DS* pvert2 = vert2;
        GLuint iiv = 0;
        for(int z=0; z<vertex_count; z++) {
          sharings[z].v = pvert2;
          sharings[z].iv = iiv;
          for(int q=sharings[z].splits; q; q--) {
            *pvert2 = vert[z];
            pvert2++;
            iiv++;
          }
        }

        iface Iface2[face_count];
        for(int z=0; z<face_count; z++)
          Iface2[z] = Iface[z];

        for(int z=0; z<vertex_count; z++) {
          ftable* fp = sharings[z].p;
          for(int q=0; q<sharings[z].numfaces; q++) {
            iface* pi_src = &Iface2[fp->fi];
            iface* pi_dst = &Iface[fp->fi];
            GLuint sv = sharings[z].iv + fp->group - 1;
            if(z==pi_src->a) pi_dst->a = sv;
            if(z==pi_src->b) pi_dst->b = sv;
            if(z==pi_src->c) pi_dst->c = sv;
            fp++;
          }  
        }

        if (API3DS_TEXT_OUT) cout<< "         Before: "<<vertex_count*3<<" After: "<<vertex_count2<<endl;
          
        iface* pif = Iface;
        for(int z=0; z<face_count; z++, pif++) {
          GLfloat nx = (pif->N).x;
          GLfloat ny = (pif->N).y;
          GLfloat nz = (pif->N).z;
          GLuint aa = pif->a;
          GLuint bb = pif->b;
          GLuint cc = pif->c;
          vert2[aa].i += nx;
          vert2[aa].j += ny;
          vert2[aa].k += nz;
          vert2[bb].i += nx;
          vert2[bb].j += ny;
          vert2[bb].k += nz;
          vert2[cc].i += nx;
          vert2[cc].j += ny;
          vert2[cc].k += nz;
        }
        for(int z=0; z<vertex_count2; z++) {
          GLfloat len = 1.0/sqrt(vert2[z].i*vert2[z].i +
                                 vert2[z].j*vert2[z].j +
                                 vert2[z].k*vert2[z].k);
          vert2[z].i *= len;
          vert2[z].j *= len;
          vert2[z].k *= len;
        }
          
        object->Vertices(vertex_count2, vert2);
            
        List<chunk3ds> pmlist(subchunks_by_type(pfaceschunk, FACES_MATERIALS, (char *)(pfaces+face_count)));
        if(pmlist.size()!=0) if (API3DS_TEXT_OUT) cout<<"    Materials: ";
        pmlist.rewind();
        GLuint* pind;
        for(int q=pmlist.size(); q; q--)
        {
          p = (*pmlist).ptr;
          ++pmlist;
          if (API3DS_TEXT_OUT) cout<<(const char *)(p+6);
          Material3DS *matpointer = scene->GetMaterial((const char *)(p+6));
          if(matpointer->Transparency()==50)
            istransparent = true;
          p+=6;
          while(*p) p++; p++;
          int mapped = getshort(p);
          if(!mapped)
            // this really happens
            continue;
          if (API3DS_TEXT_OUT) cout<<" ("<<mapped<<"), ";
          p+=2;

          pind = new GLuint[mapped*3];
          object->AddFaceGroup(matpointer, mapped, pind);
          for(int w=mapped; w; w--, p+=2) {
            iface* pif = Iface+getshort(p);
            pif->hasmaterial = true;
            *pind = pif->a; pind++;
            *pind = pif->b; pind++;
            *pind = pif->c; pind++;
          }
        }
        if(pmlist.size()!=0)
          if (API3DS_TEXT_OUT) cout<<endl;

        // DEFAULT material (for faces without assigned material)

        int wo_material = 0;
        for(int w=0; w<face_count; w++)
          if(!(Iface[w].hasmaterial))
            wo_material++;
        if(wo_material) {
          pind = new GLuint[wo_material*3];
          Material3DS *defmat = scene->GetMaterial("____DEFAULT_MATERIAL");
          object->AddFaceGroup(defmat, wo_material, pind);
          for(int w=0; w<face_count; w++) {
            iface* pif = Iface+w;
            if(!(pif->hasmaterial)) {
              *pind = pif->a; pind++;
              *pind = pif->b; pind++;
              *pind = pif->c; pind++;
            }
          }
        }

        if(istransparent)
          transparent_objects+=object;
        else
          scene->Add(object);
  
      }

      continue;
    }
 
    // light
    if(find_chunk(po, LIGHT, pi))
    {
      if (API3DS_TEXT_OUT) cout << "A light." << endl;
      p = find_chunk(po, LIGHT, pi);
      char *vec = p+6;
      char *prgb = find_chunk(p, RGBfloat, p+18)+6;
      if (API3DS_TEXT_OUT) cout<<"     Position: ("
         << getfloat(vec) <<", "
         << getfloat(vec+4) <<", "
         << getfloat(vec+8) << ")" << endl;
      if (API3DS_TEXT_OUT) cout<<"     Color: ("
         <<(int)(256*getfloat(prgb))<<", "
         <<(int)(256*getfloat(prgb+4))<<", "
         <<(int)(256*getfloat(prgb+8))<< ")" << endl;
      OmniLight3DS* light = new OmniLight3DS;
      light->Name(pname);
      scene->Add(light);
      light->Position(getfloat(vec), getfloat(vec+4), getfloat(vec+8));
      light->Diffuse(getfloat(prgb), getfloat(prgb+4), getfloat(prgb+8));
/*
        if(FindChunk(plights, LIGHT_OFF, plights+18))
          {
            newlight->state = 0;
            printf(" OFF");
          }
*/
        continue;
      }

    // object identified as a camera
    if(find_chunk(po, CAMERA, pi))
    {
      if (API3DS_TEXT_OUT) cout << "A camera." << endl;
      CameraTarget3DS *camera = new CameraTarget3DS;
      camera->Name(pname);
      scene->Add(camera);
      p = find_chunk(po, CAMERA, pi);
      char *vec = p+6;
      if (API3DS_TEXT_OUT) cout<<"     Origin: ("
         << getfloat(vec) <<", "
         << getfloat(vec+4) <<", "
         << getfloat(vec+8) << ")" << endl;
      camera->Origin(getfloat(vec), getfloat(vec+4), getfloat(vec+8));
      vec+=12;
      if (API3DS_TEXT_OUT) cout<<"     Target: ("
         << getfloat(vec) <<", "
         << getfloat(vec+4) <<", "
         << getfloat(vec+8) << ")" << endl;
      camera->Target(getfloat(vec), getfloat(vec+4), getfloat(vec+8));
      vec+=12;
      if (API3DS_TEXT_OUT) cout<<"    Bank: "<<getfloat(vec)<<endl;
      vec+=4;
      if (API3DS_TEXT_OUT) cout<<"    Lens: "<<getfloat(vec)<<endl;
      continue;
    }

  }

  scene->Add(transparent_objects);

  if (API3DS_TEXT_OUT) cout << endl;

  // Keyframer chunk
  // ------------------------------------------------------------------

  char *keyframer = find_chunk(pmesh, KEYFRAMER_CHUNK, pmesh+6);
  if(!keyframer) {
    throw 1;
  }
  if (API3DS_TEXT_OUT) cout << "Keyframer data:" << endl << endl;

  p = find_chunk(keyframer, FRAMES, keyframer+6);
  if(!p) {
    throw 1;
  }
  if (API3DS_TEXT_OUT) cout << "Animation frames: "<<(int)getlong(p+6)<<" to "<<(int)getlong(p+10)<<"."<<endl;
  int frames_start = (int)getlong(p+6);
  int frames_end = (int)getlong(p+10);
  int frames = frames_end-frames_start + 1;
  scene->Frames(frames);

  List<chunk3ds> camo(subchunks_by_type(keyframer, CAMERA_INFORMATION_BLOCK, keyframer+6));
  camo.rewind();
  for(int nc=camo.size(); nc; nc--) 
  {
    if (API3DS_TEXT_OUT) cout << "Camera origin info block:" << endl;
    char *info = (*camo).ptr;
    ++camo;

    char *objinfo = find_chunk(info, OBJECT_INFO, info+6);
    pname = (const char *)(objinfo+6);
    if (API3DS_TEXT_OUT) cout << "  Camera: " << pname << endl;
    CameraTarget3DS *camera = dynamic_cast<CameraTarget3DS*>(scene->GetCamera(pname));
    unsigned short flag, tracktype;
    unsigned long keys;

    char *track = find_chunk(info, POSITION_TRACK, info+6);
    if(track) {
      track+=6;
      flag = getshort(track);
      if (API3DS_TEXT_OUT) cout << "    track type: ";
      track_type_info(flag);
      if (API3DS_TEXT_OUT) cout << endl;
      tracktype = flag;

      track+=10;
      if (API3DS_TEXT_OUT) cout << "    Position keys: " << (int)getlong(track) << endl;
      keys = (int)getlong(track);
      track+=4;
    
      Dynamic<Vector3f, GLfloat> *newtrack = process_position_track(frames, keys, track, tracktype);
      if(newtrack)
        camera->Origin(newtrack, true);
    }

    track = find_chunk(info, ROLL_TRACK, info+6);
    if(track) {
      track+=6;
      flag = getshort(track);
      if (API3DS_TEXT_OUT) cout << "    track type: ";
      track_type_info(flag);
      if (API3DS_TEXT_OUT) cout << endl;
      tracktype = flag;

      track+=10;
      if (API3DS_TEXT_OUT) cout << "    Roll keys: " << (int)getlong(track) << endl;
      keys = (int)getlong(track);
      track+=4;
    
      Dynamic<GLfloat, GLfloat> *newtrack = process_roll_track(frames, keys, track, tracktype);
      if(newtrack)
        camera->Roll(newtrack, true);
    }

  }

  List<chunk3ds> camt(subchunks_by_type(keyframer, CAMERA_TARGET_INFORMATION_BLOCK, keyframer+6));
  camt.rewind();
  for(int nc=camt.size(); nc; nc--) 
  {
    if (API3DS_TEXT_OUT) cout << "Camera target info block:" << endl;
    char *info = (*camt).ptr;
    ++camt;

    char *objinfo = find_chunk(info, OBJECT_INFO, info+6);
    pname = (const char *)(objinfo+6);
    if (API3DS_TEXT_OUT) cout << "  Camera: " << pname << endl;

    char *track = find_chunk(info, POSITION_TRACK, info+6);
    if(!track)
      continue;

    track+=6;
    unsigned short flag = getshort(track);
    if (API3DS_TEXT_OUT) cout << "    track type: ";
    track_type_info(flag);
    if (API3DS_TEXT_OUT) cout << endl;
    unsigned short tracktype = flag;

    track+=10;
    if (API3DS_TEXT_OUT) cout << "    Position keys: " << (int)getlong(track) << endl;
    unsigned long keys = (int)getlong(track);
    track+=4;
    
    Dynamic<Vector3f, GLfloat> *newtrack = process_position_track(frames, keys, track, tracktype);
    if(!newtrack)
      continue;

    CameraTarget3DS *camera = dynamic_cast<CameraTarget3DS*>(scene->GetCamera(pname));
    if(!camera)
      continue;
    camera->Target(newtrack, true);
  }

  List<chunk3ds> caml(subchunks_by_type(keyframer, OMNI_LIGHT_INFORMATION_BLOCK, keyframer+6));
  caml.rewind();
  for(int nc=caml.size(); nc; nc--)
  {
    if (API3DS_TEXT_OUT) cout << "Omnilight info block:" << endl;
    char *info = (*caml).ptr;
    ++caml;

    char *objinfo = find_chunk(info, OBJECT_INFO, info+6);
    pname = (const char *)(objinfo+6);
    if (API3DS_TEXT_OUT) cout << "  Omnilight: " << pname << endl;

    Dynamic<Vector3f, GLfloat> *newtrack;
    
    unsigned short flag, tracktype;
    unsigned long keys;

    OmniLight3DS *light = dynamic_cast<OmniLight3DS*>(scene->GetLight(pname));

    char *track = find_chunk(info, POSITION_TRACK, info+6);
    if(track) {
      track+=6;
      flag = getshort(track);
      if (API3DS_TEXT_OUT) cout << "    track type: ";
      track_type_info(flag);
      if (API3DS_TEXT_OUT) cout << endl;
      tracktype = flag;

      track+=10;
      if (API3DS_TEXT_OUT) cout << "    Position keys: " << (int)getlong(track) << endl;
      keys = (unsigned long)getlong(track);
      track+=4;

      newtrack = process_position_track(frames, keys, track, tracktype);
      if(newtrack) 
        light->Position(newtrack, true);
    }

    track = find_chunk(info, COLOR_TRACK, info+6);
    if(track) {
      track+=6;
      flag = getshort(track);
      if (API3DS_TEXT_OUT) cout << "    track type: ";
      track_type_info(flag);
      if (API3DS_TEXT_OUT) cout << endl;
      tracktype = flag;

      track+=10;
      if (API3DS_TEXT_OUT) cout << "    Color keys: " << (int)getlong(track) << endl;
      keys = (unsigned long)getlong(track);
      track+=4;

      newtrack = process_position_track(frames, keys, track, tracktype);
      if(newtrack) 
        light->Diffuse(newtrack, true);
    }

  }

    throw 1;
  }
  catch(int value) {
    if(take_care_of_buf_alloc) {
      delete[] buffer;
      buffer = 0;
    }
    return scene;
  }
}

inline float
hermite_spline(float t, float P1, float R1, float P2, float R2) {
  return P1*(2*t*t*t - 3*t*t + 1) +
         R1*(t*t*t - 2*t*t + t) +
         P2*(-2*t*t*t + 3*t*t) +
         R2*(t*t*t - t*t);
}

Vector3f Loader3DS::position_track::getv(int sel, int n) {
  position_key *kn_1, *kn, *kn1;
  Vector3f *pn_1, *pn, *pn1;
  int d1, d2;

  kn = &keys[n];
  pn = &kn->p;

  if (sel == point) return *pn;

  if (n == 0) {
    //first key
    kn1 = &keys[1];
    pn1 = &kn1->p;

    if (count == 2) {
      //2 keys
      return (*pn1 - *pn)*(1.0F - kn->tension);
    };
    if (mode != 3) {
      //first key, no loop
      return ((*pn1 - *pn)*1.5F - getv(an,1)*0.5F)*(1.0F - kn->tension);
    } else {
      //first key, loop
      kn_1= &keys[count-2];
      d1 = keys[count-1].frame - kn_1->frame;
      d2 = kn1->frame - kn->frame;
    };
  } else if (n == count-1) {
    //last key
    kn_1 = &keys[n-1];
    pn_1 = &kn_1->p;

    if (count == 2) {
      //2 keys
      return (*pn - *pn_1)*(1.0F - kn->tension);
    };
    if (mode != 3) {
      //last key, no loop
      return ((*pn - *pn_1)*1.5F - getv(bn,n-1)*0.5F)*(1.0F - kn->tension);
    } else {
      //last key, loop
      kn1 = &keys[1];
      d1 = kn->frame - kn_1->frame;
      d2 = kn1->frame - keys[0].frame;
    };
  } else {
    //middle keys
    kn_1= &keys[n-1];
    kn1 = &keys[n+1];
    d1 = kn->frame - kn_1->frame;
    d2 = kn1->frame - kn->frame;
  };
  pn_1= &kn_1->p;
  pn1 = &kn1->p;

  float C;
  float adjust;

  if (sel == an) {
    C = kn->continuity;
    adjust = d1;
  } else {
    C = -kn->continuity;
    adjust = d2;
  };
  adjust /= d1 + d2;
  adjust = 0.5 + (1.0 - fabs(C))*(adjust - 0.5);

  return (   (*pn  - *pn_1) * ((1.0F + kn->bias)*(1.0F - C))
           + (*pn1 - *pn  ) * ((1.0F - kn->bias)*(1.0F + C))
         ) * ((1.0F - kn->tension)*adjust);
};

Dynamic<Vector3f, GLfloat>* Loader3DS::process_position_track(int allframes, int keys, char *track, unsigned short tracktype)
{
  if(keys<2)
    return 0;

  position_track keyinfo(keys, tracktype&3);

  for(int g=0; g<keys; g++)
  {
    if (API3DS_TEXT_OUT) cout << "    Key " << g << ": Frame " << (int)getlong(track) << ".: ";
    keyinfo[g].frame = (int)getlong(track);
    track+=4;
    unsigned short flag = getshort(track);
    tcb_info(flag);
    track = fill_tcbee(flag, track+2, &keyinfo[g]);
    float x = getfloat(track);
    float y = getfloat(track+4);
    float z = getfloat(track+8);

    keyinfo[g].p = Vector3f(x, y, z);

    if (API3DS_TEXT_OUT) cout << "  (" << x << ", " << y << ", " << z << ")" << endl;
    track+=12;
  }

  int first_frame = keyinfo[0].frame;
  int last_frame = keyinfo[keys-1].frame;

  int frames = last_frame-first_frame + 1;

  Vector3f *curve = new Vector3f[frames];

  Vector3f t1, t2;
  int k = 0;
  for(int g=first_frame; g<=last_frame; g++)
  {
    if(g>(keyinfo[k+1].frame)) {
      k++;
    }
    int d = keyinfo[k+1].frame - keyinfo[k].frame;
    double t = (double)(g-keyinfo[k].frame)/(double)d;
    t1 = keyinfo.getv(bn, k);
    t2 = keyinfo.getv(an, k+1);
    float xx = hermite_spline(t, (keyinfo[k].p).x, t1.x, (keyinfo[k+1].p).x, t2.x);
    float yy = hermite_spline(t, (keyinfo[k].p).y, t1.y, (keyinfo[k+1].p).y, t2.y);
    float zz = hermite_spline(t, (keyinfo[k].p).z, t1.z, (keyinfo[k+1].p).z, t2.z);
    curve[g-first_frame] = Vector3f(xx, yy, zz);
  }

  DvectorLinearFramed<Vector3f, GLfloat> *newtrack = new DvectorLinearFramed<Vector3f, GLfloat>;
  newtrack->Frames(allframes, frames, first_frame, last_frame, curve);
  return newtrack;
}

GLfloat Loader3DS::roll_track::getv(int sel, int n) {
  roll_key *kn_1, *kn, *kn1;
  GLfloat *pn_1, *pn, *pn1;
  int d1, d2;

  kn = &keys[n];
  pn = &kn->p;

  if (sel == point) return *pn;

  if (n == 0) {
    //first key
    kn1 = &keys[1];
    pn1 = &kn1->p;

    if (count == 2) {
      //2 keys
      return (*pn1 - *pn)*(1.0F - kn->tension);
    };
    if (mode != 3) {
      //first key, no loop
      return ((*pn1 - *pn)*1.5F - getv(an,1)*0.5F)*(1.0F - kn->tension);
    } else {
      //first key, loop
      kn_1= &keys[count-2];
      d1 = keys[count-1].frame - kn_1->frame;
      d2 = kn1->frame - kn->frame;
    };
  } else if (n == count-1) {
    //last key
    kn_1 = &keys[n-1];
    pn_1 = &kn_1->p;

    if (count == 2) {
      //2 keys
      return (*pn - *pn_1)*(1.0F - kn->tension);
    };
    if (mode != 3) {
      //last key, no loop
      return ((*pn - *pn_1)*1.5F - getv(bn,n-1)*0.5F)*(1.0F - kn->tension);
    } else {
      //last key, loop
      kn1 = &keys[1];
      d1 = kn->frame - kn_1->frame;
      d2 = kn1->frame - keys[0].frame;
    };
  } else {
    //middle keys
    kn_1= &keys[n-1];
    kn1 = &keys[n+1];
    d1 = kn->frame - kn_1->frame;
    d2 = kn1->frame - kn->frame;
  };
  pn_1= &kn_1->p;
  pn1 = &kn1->p;

  float C;
  float adjust;

  if (sel == an) {
    C = kn->continuity;
    adjust = d1;
  } else {
    C = -kn->continuity;
    adjust = d2;
  };
  adjust /= d1 + d2;
  adjust = 0.5 + (1.0 - fabs(C))*(adjust - 0.5);

  return (   (*pn  - *pn_1) * ((1.0F + kn->bias)*(1.0F - C))
           + (*pn1 - *pn  ) * ((1.0F - kn->bias)*(1.0F + C))
         ) * ((1.0F - kn->tension)*adjust);
};

Dynamic<GLfloat, GLfloat>* Loader3DS::process_roll_track(int allframes, int keys, char *track, unsigned short tracktype)
{
  if(keys<2)
    return 0;

  roll_track keyinfo(keys, tracktype&3);

  for(int g=0; g<keys; g++)
  {
    if (API3DS_TEXT_OUT) cout << "    Key " << g << ": Frame " << (int)getlong(track) << ".: ";
    keyinfo[g].frame = (int)getlong(track);
    track+=4;
    unsigned short flag = getshort(track);
    tcb_info(flag);
    track = fill_tcbee(flag, track+2, &keyinfo[g]);
    float x = getfloat(track);

    keyinfo[g].p = x;

    if (API3DS_TEXT_OUT) cout << "  " << x << endl;
    track+=4;
  }

  int first_frame = keyinfo[0].frame;
  int last_frame = keyinfo[keys-1].frame;

  int frames = last_frame-first_frame + 1;

  GLfloat *curve = new GLfloat[frames];

  GLfloat t1, t2;
  int k = 0;
  for(int g=first_frame; g<=last_frame; g++)
  {
    if(g>(keyinfo[k+1].frame)) {
      k++;
    }
    int d = keyinfo[k+1].frame - keyinfo[k].frame;
    double t = (double)(g-keyinfo[k].frame)/(double)d;
    t1 = keyinfo.getv(bn, k);
    t2 = keyinfo.getv(an, k+1);
    float xx = hermite_spline(t, keyinfo[k].p, t1, keyinfo[k+1].p, t2);
    curve[g-first_frame] = xx;
  }

  DvectorLinearFramed<GLfloat, GLfloat> *newtrack = new DvectorLinearFramed<GLfloat, GLfloat>;
  newtrack->Frames(allframes, frames, first_frame, last_frame, curve);
  return newtrack;
}
