#ifndef _vlacik2_H
#define _vlacik2_H

#define unit_length 100.0
#define straight_line_max (unit_length/5.0)
#define rail_spacing 10.0
#define rail_size 1.4
#define tube_sides 9
#define tube_radius (rail_spacing/2.0+12.0)
#define rail_tube_spacing 6.0

//  Sorts of tracks

#define STRAIGHT                1
#define HALFTURN_RIGHT          2
#define QUARTERTURN_RIGHT       3
#define UTURN_RIGHT             4
#define HALFTURN_LEFT           5
#define QUARTERTURN_LEFT        6
#define UTURN_LEFT              7
#define HALFTURN_UP             8
#define QUARTERTURN_UP	        9
#define UTURN_UP                10
#define HALFTURN_DOWN           11
#define QUARTERTURN_DOWN        12
#define UTURN_DOWN              13
#define ASCEND                  14
#define DESCEND                 15
#define ASCEND_LOW              16
#define DESCEND_LOW             17
#define ASCEND_HIGH             18
#define DESCEND_HIGH            19
#define FULLWHIRL               20
#define FULLWHIRL_CLOCKWISE     21
#define HALFWHIRL               22
#define HALFWHIRL_CLOCKWISE     23
#define DOUBLEWHIRL             24
#define DOUBLEWHIRL_CLOCKWISE   25
#define LOOPING                 26

//  Construction style flags

#define HAS_TUBE	1


struct track_base
{
  static Material3DS* rail_material;
  static Material3DS* tube_material;
  unsigned long flag;

  GLmatrix_double cs;
  quaternion<double> qcs;

  track_base() {
    cs.Identity();
    qcs = quaternion<double> (1.0, 0.0, 0.0, 0.0);
    flag = 0;
  }
  void CS(const GLmatrix_double& m) {
    cs=m;
  };
  void QCS(const quaternion<double>& q) {
    qcs = q;
  };

  virtual double length() = 0;
  virtual Vector3d position(double t) = 0;
  virtual Vector3d direction(double t) = 0;
  virtual Vector3d udirection(double t) = 0;
  virtual double roll(double t) = 0;
  virtual quaternion<double> local_orientation(double t) = 0;
  virtual RenderableEntityA* rails() = 0;

  void normalize(Vector3d &u) {
    double l = 1.0/sqrt(u.x*u.x + u.y*u.y + u.z+u.z);
    u.x*=l; u.y*=l; u.z*=l;
  }
  GLmatrix_double answer_cs(const Vector3d &p, const Vector3d &d, const Vector3d &ud, double r) {
    Vector3d n, u, v, a,b;
    n=d;
    u=ud;
    v.x=-(u.y*n.z-u.z*n.y);
    v.y=-(u.z*n.x-u.x*n.z);
    v.z=-(u.x*n.y-u.y*n.x);
    double s = sin(r);
    double c = cos(r);
    a = c*u + s*v;
    b = -s*u + c*v;
    GLmatrix_double m;
    m[0]=a.x; m[4]=b.x; m[8]= n.x; m[12]=p.x;
    m[1]=a.y; m[5]=b.y; m[9]= n.y; m[13]=p.y;
    m[2]=a.z; m[6]=b.z; m[10]=n.z; m[14]=p.z;
    m[3]=0.0; m[7]=0.0; m[11]=0.0; m[15]=1.0;
    return m;
  }
  virtual GLmatrix_double coordinate_system(double t) {
    return cs*answer_cs(position(t), direction(t), udirection(t), roll(t));
  }
  virtual quaternion<double> orientation(double t) {
    return qcs*local_orientation(t);
  }

  virtual RenderableEntityA* tube() {
    if(!(flag&HAS_TUBE))
      return 0;
    GLmatrix_double m;
    double d = rail_spacing/2.0;
    int parts = (int)(length()/straight_line_max)+1;
    Vector3d P, X, Y, V, H, N;
    Vertex3DS* v = new Vertex3DS[tube_sides*(parts+1)];

    GLuint i = 0;

    for(int q=0; q<=parts; q++) {
      m = coordinate_system((double)q/(double)parts);
      P.x = m[12]; P.y = m[13]; P.z = m[14];
      X.x = m[0]; X.y = m[1]; X.z = m[2];
      Y.x = m[4]; Y.y = m[5]; Y.z = m[6];
      H = Y*sqrt(tube_radius*tube_radius - (d+rail_tube_spacing)*(d+rail_tube_spacing));

      for(int k=0; k<tube_sides; k++) {
        double a = ((2*PI)/(double)tube_sides)*k;
        N = X*cos(a) + Y*sin(a);
        V = P + H + tube_radius*N;
        v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
        v[i].i = N.x; v[i].j = N.y; v[i].k = N.z;
        v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
        v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
        i++;
      }
    }

    RenderableEntityA* obj = new RenderableEntityA;
    obj->SetMaterial(tube_material);
    obj->Vertices(tube_sides*(parts+1), v);

    int isize = (parts+1)<<1;
    GLuint* ind[tube_sides];
    for(int k=0; k<tube_sides; k++)
      ind[k] = new GLuint[isize];

    i = 0; int j = 0;
    for(int q=0; q<=parts; q++, i+=tube_sides, j+=2) {
      for(int k=0; k<tube_sides; k++) {
        *(ind[k]+j) = i+k;
        *(ind[k]+j+1) = i+((k+1)%tube_sides);
      }
    }

    for(int k=0; k<tube_sides; k++)
      obj->AddElement(GL_TRIANGLE_STRIP, isize, ind[k]);

    return obj;
  }
};

// initialization of static data

Material3DS* track_base::rail_material = 0;
Material3DS* track_base::tube_material = 0;

// STRAIGHT

struct straight_track : public track_base
{
  double len;
  void Length(double f) { len=f; }
  virtual double length() { return len; }

  virtual Vector3d position(double t) {
    return Vector3d(0.0, 0.0, t*len);
  }
  virtual Vector3d direction(double t) {
    return Vector3d(0.0, 0.0, 1.0);
  }
  virtual Vector3d udirection(double t) {
    return Vector3d(1.0, 0.0, 0.0);
  }
  virtual double roll(double t) {
    return 0;
  }
  virtual quaternion<double> local_orientation(double t) {
    return quaternion<double> (1.0, 0.0, 0.0, 0.0);
  }
  virtual RenderableEntityA* rails() {
    GLmatrix_double m;
    double d = rail_spacing/2.0;
    int parts = 1;
    Vector3d P, X, Y, V;
    Vertex3DS* v = new Vertex3DS[6*(parts+1)];

    GLuint i = 0;

    for(int q=0; q<=parts; q++) {
      m = coordinate_system((double)q/(double)parts);
      P.x = m[12]; P.y = m[13]; P.z = m[14];
      X.x = m[0]; X.y = m[1]; X.z = m[2];
      Y.x = m[4]; Y.y = m[5]; Y.z = m[6];

      V = P - X*(d + rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = -X.x; v[i].j = -X.y; v[i].k = -X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P - X*(d - rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = X.x; v[i].j = X.y; v[i].k = X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P - X*d + Y*rail_size;
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = Y.x; v[i].j = Y.y; v[i].k = Y.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;

      V = P + X*(d - rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = -X.x; v[i].j = -X.y; v[i].k = -X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P + X*(d + rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = X.x; v[i].j = X.y; v[i].k = X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P + X*d + Y*rail_size;
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = Y.x; v[i].j = Y.y; v[i].k = Y.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
    }

    RenderableEntityA* obj = new RenderableEntityA;
    obj->SetMaterial(rail_material);
    obj->Vertices(6*(parts+1), v);
    int isize = (parts+1)<<1;
    GLuint* ind1 = new GLuint[isize];
    GLuint* ind2 = new GLuint[isize];
    GLuint* ind3 = new GLuint[isize];
    GLuint* ind4 = new GLuint[isize];
    GLuint* ind5 = new GLuint[isize];
    GLuint* ind6 = new GLuint[isize];

    i = 0; int j = 0;
    for(int q=0; q<=parts; q++, i+=6, j+=2) {
      ind1[j] = i;
      ind1[j+1] = i+1;

      ind2[j] = i+1;
      ind2[j+1] = i+2;

      ind3[j] = i+2;
      ind3[j+1] = i;

      ind4[j] = i+3;
      ind4[j+1] = i+4;

      ind5[j] = i+4;
      ind5[j+1] = i+5;

      ind6[j] = i+5;
      ind6[j+1] = i+3;
    }

    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind1);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind2);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind3);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind4);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind5);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind6);

    return obj;
  }
  
};

// curved base

struct track_base_curved : public track_base
{
  virtual RenderableEntityA* rails() {
    GLmatrix_double m;
    double d = rail_spacing/2.0;
    int parts = (int)(length()/straight_line_max)+1;
    Vector3d P, X, Y, V;
    Vertex3DS* v = new Vertex3DS[6*(parts+1)];

    GLuint i = 0;

    for(int q=0; q<=parts; q++) {
      m = coordinate_system((double)q/(double)parts);
      P.x = m[12]; P.y = m[13]; P.z = m[14];
      X.x = m[0]; X.y = m[1]; X.z = m[2];
      Y.x = m[4]; Y.y = m[5]; Y.z = m[6];

      V = P - X*(d + rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = -X.x; v[i].j = -X.y; v[i].k = -X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P - X*(d - rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = X.x; v[i].j = X.y; v[i].k = X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P - X*d + Y*rail_size;
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = Y.x; v[i].j = Y.y; v[i].k = Y.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
  
      V = P + X*(d - rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = -X.x; v[i].j = -X.y; v[i].k = -X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P + X*(d + rail_size*0.5);
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = X.x; v[i].j = X.y; v[i].k = X.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
      V = P + X*d + Y*rail_size;
      v[i].x = V.x; v[i].y = V.y; v[i].z = V.z; v[i].w = 1.0F;
      v[i].i = Y.x; v[i].j = Y.y; v[i].k = Y.z;
      v[i].R = v[i].G = v[i].B = v[i].A = 1.0F;
      v[i].s = v[i].t = v[i].r = 0.0F; v[i].q = 1.0F;
      i++;
    }

    RenderableEntityA* obj = new RenderableEntityA;
    obj->SetMaterial(rail_material);
    obj->Vertices(6*(parts+1), v);
    int isize = (parts+1)<<1;
    GLuint* ind1 = new GLuint[isize];
    GLuint* ind2 = new GLuint[isize];
    GLuint* ind3 = new GLuint[isize];
    GLuint* ind4 = new GLuint[isize];
    GLuint* ind5 = new GLuint[isize];
    GLuint* ind6 = new GLuint[isize];

    i = 0; int j = 0;
    for(int q=0; q<=parts; q++, i+=6, j+=2) {
      ind1[j] = i;
      ind1[j+1] = i+1;

      ind2[j] = i+1;
      ind2[j+1] = i+2;

      ind3[j] = i+2;
      ind3[j+1] = i;

      ind4[j] = i+3;
      ind4[j+1] = i+4;

      ind5[j] = i+4;
      ind5[j+1] = i+5;

      ind6[j] = i+5;
      ind6[j+1] = i+3;
    }

    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind1);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind2);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind3);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind4);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind5);
    obj->AddElement(GL_TRIANGLE_STRIP, isize, ind6);

    return obj;
  }
};

// circular base

struct circular_track : public track_base_curved
{
  double a, b;
  double radius;
  double clk;
  circular_track() : a(0), b(0), radius(0), clk(1) {}
  void A(double f) { a=f; }
  void B(double f) { b=f; }
  void Radius(double f) { radius=f; }
  void Clk(double f) { clk=f; }
  virtual double length() { return radius*fabs(a-b); }
};

// HORIZONTAL

struct turn_track_horizontal : public circular_track
{
  virtual Vector3d position(double t) {
    double tt = a + t*(b-a);
    return Vector3d(radius*(cos(tt)-cos(a)),
                    0.0,
                    radius*(sin(tt)-sin(a)));
  }
  virtual Vector3d direction(double t) {
    double tt = a + t*(b-a);
    return Vector3d(-sin(tt)*clk, 0.0, cos(tt)*clk);
  }
  virtual Vector3d udirection(double t) {
    double tt = a + t*(b-a);
    return Vector3d(cos(tt)*clk, 0.0, sin(tt)*clk);
  }
  virtual quaternion<double> local_orientation(double t) {
    return AngleAxis2Quaternion(-t*(b-a), 0.0, 1.0, 0.0);
  }
  virtual double roll(double t) {
    return 0;
  }
};

struct halfturn_left : public turn_track_horizontal {
  halfturn_left() { a=PI; b=PI/2.0; clk=-1.0; }
};
struct quarterturn_left : public turn_track_horizontal {
  quarterturn_left() { a=PI; b=(3*PI)/4.0; clk=-1.0; }
};
struct uturn_left : public turn_track_horizontal {
  uturn_left() { a=PI; b=0.0; clk=-1.0; }
};

struct halfturn_right : public turn_track_horizontal {
  halfturn_right() { a=0.0; b=PI/2.0; clk=1.0; }
};
struct quarterturn_right : public turn_track_horizontal {
  quarterturn_right() { a=0.0; b=PI/4.0; clk=1.0; } 
};
struct uturn_right : public turn_track_horizontal {
  uturn_right() { a=0.0; b=PI; clk=1.0; }
};

// VERTICAL

struct turn_track_vertical : public circular_track
{
  virtual Vector3d position(double t) {
    double tt = a + t*(b-a);
    return Vector3d(0.0,
                    radius*(sin(tt)-sin(a)),
                    radius*(cos(tt)-cos(a)));
  }
  virtual Vector3d direction(double t) {
    double tt = a + t*(b-a);
    return Vector3d(0.0, cos(tt)*clk, -sin(tt)*clk);
  }
  virtual Vector3d udirection(double t) {
    double tt = a + t*(b-a);
    return Vector3d(1.0, 0.0, 0.0);
  }
  virtual double roll(double t) {
    return 0;
  }
  virtual quaternion<double> local_orientation(double t) {
    return AngleAxis2Quaternion(-t*(b-a), 1.0, 0.0, 0.0);
  }
};

struct ascend : public turn_track_vertical {
  ascend() { a=(3*PI)/2.0; b=(7*PI)/4.0; clk=1.0; } 
};
struct descend : public turn_track_vertical {
  descend() { a=PI/2.0; b=PI/4.0; clk=-1.0; }
};
struct ascend_low : public turn_track_vertical {
  ascend_low() { a=(3*PI)/2.0; b=(5*PI)/3.0; clk=1.0; } 
};
struct descend_low : public turn_track_vertical {
  descend_low() { a=PI/2.0; b=PI/3.0; clk=-1.0; }
};
struct ascend_high : public turn_track_vertical {
  ascend_high() { a=(3*PI)/2.0; b=(11*PI)/6.0; clk=1.0; } 
};
struct descend_high : public turn_track_vertical {
  descend_high() { a=PI/2.0; b=PI/6.0; clk=-1.0; }
};

struct halfturn_down : public turn_track_vertical {
  halfturn_down() { a=PI/2.0; b=0.0; clk=-1.0; }
};
struct quarterturn_down : public turn_track_vertical {
  quarterturn_down() { a=PI/2.0; b=PI/4.0; clk=-1.0; }
};
struct uturn_down : public turn_track_vertical {
  uturn_down() { a=PI/2.0; b=-PI/2.0; clk=-1.0; }
};

struct halfturn_up : public turn_track_vertical {
  halfturn_up() { a=(3*PI)/2.0; b=2*PI; clk=1.0; }
};
struct quarterturn_up : public turn_track_vertical {
  quarterturn_up() { a=(3*PI)/2.0; b=(7*PI)/4.0; clk=1.0; } 
};
struct uturn_up : public turn_track_vertical {
  uturn_up() { a=(3*PI)/2.0; b=(5*PI)/2; clk=1.0; }
};

// LOOPINGS

struct loopings : public circular_track
{
  virtual Vector3d position(double t) {
    Vector3d v;
    double tt = a + t*(b-a);
    v.x = (rail_spacing)*sin(t*PI/2.0)*sin(t*PI/2.0);
    v.y = radius*(sin(tt)-sin(a));
    v.z = radius*(cos(tt)-cos(a));
    return v;
  }
  virtual Vector3d direction(double t) {
    double tt = a + t*(b-a);
    return Vector3d(0.0, cos(tt)*clk, -sin(tt)*clk);
  }
  virtual Vector3d udirection(double t) {
    return Vector3d(1.0, 0.0, 0.0);
  }
  virtual double roll(double t) {
    return 0;
  }
  virtual quaternion<double> local_orientation(double t) {
    return AngleAxis2Quaternion(-t*(b-a), 1.0, 0.0, 0.0);
  }
};

struct looping : public loopings {
  looping() { a=(3*PI)/2.0; b=(7*PI)/2.0; clk=1.0; }
};


// WHIRL

struct whirl_track : public track_base_curved
{
  double len;
  double a;
  double clk;
  whirl_track() : a(0), len(0), clk(1) {}
  void Degree(double f) { a=f; }
  void Length(double f) { len=f; }
  void Clk(double f) { clk=f; }
  virtual double length() { return len; }
  virtual Vector3d position(double t) {
    return Vector3d(0.0, 0.0, t*len);
  }
  virtual Vector3d direction(double t) {
    return Vector3d(0.0, 0.0, 1.0);
  }
  virtual Vector3d udirection(double t) {
    return Vector3d(1.0, 0.0, 0.0);
  }
  virtual double roll(double t) {
    return clk*sin(t*PI/2.0)*sin(t*PI/2.0)*a;
  }
  virtual quaternion<double> local_orientation(double t) {
    return AngleAxis2Quaternion(roll(t), 0.0, 0.0, 1.0);
  }

};

struct fullwhirl : public whirl_track {
  fullwhirl() { a=2*PI; clk=1.0; }
};
struct fullwhirl_clockwise : public whirl_track {
  fullwhirl_clockwise() { a=2*PI; clk=-1.0; }
};
struct halfwhirl : public whirl_track {
  halfwhirl() { a=PI; clk=1.0; }
};  
struct halfwhirl_clockwise : public whirl_track {
  halfwhirl_clockwise() { a=PI; clk=-1.0; }
};  
struct doublewhirl : public whirl_track {
  doublewhirl() { a=4*PI; clk=1.0; }
};
struct doublewhirl_clockwise : public whirl_track {
  doublewhirl_clockwise() { a=4*PI; clk=-1.0; }
};

struct train_track_desc
{
  int type;
  double arg1;
  double arg2;
  unsigned long f;
};

class train_track
{ 
  List<track_base *> parts;
  RenderableEntities *construction;

  struct track_info
  {
    double length;
    double before;
    track_base *track;
  };

  int n_tracks;
  int tid;
  double track_length;
  track_info *info;

  void join_tracks() {
    track_base *ptrack;
    GLmatrix_double m;
    m.Identity();
    quaternion<double> q(1.0, 0.0, 0.0, 0.0);
    parts.rewind();
    for(int i=parts.size(); i; i--) {
      ptrack = *parts;
      ptrack->CS(m);
      ptrack->QCS(q);
      m = ptrack->coordinate_system(1.0);
      q = ptrack->orientation(1.0);
      ++parts;
    }
  }
public:
  train_track() : construction(0) {}

  double length() { return track_length; }

  void AddTrack(track_base *p) {
    if(p) parts.push_back(p);
  }
  void operator +=(track_base *p) {
    if(p) parts.push_back(p);
  }

  void AddTrack(int type, double arg1, double arg2, unsigned long flag) {
    track_base *p = 0;
    switch(type) {
      case STRAIGHT:
        p = new straight_track;
        (*(straight_track *)p).Length(arg1);
        break;
      case HALFTURN_RIGHT:
        p = new halfturn_right;
        (*(halfturn_right *)p).Radius(arg1);
        break;
      case QUARTERTURN_RIGHT:
        p = new quarterturn_right;
        (*(quarterturn_right *)p).Radius(arg1);
        break;
      case UTURN_RIGHT:
        p = new uturn_right;
        (*(uturn_right *)p).Radius(arg1);
        break;
      case HALFTURN_LEFT:
        p = new halfturn_left;
        (*(halfturn_left *)p).Radius(arg1);
        break;
      case QUARTERTURN_LEFT:
        p = new quarterturn_left;
        (*(quarterturn_left *)p).Radius(arg1);
        break;
      case UTURN_LEFT:
        p = new uturn_left;
        (*(uturn_left *)p).Radius(arg1);
        break;
      case HALFTURN_UP:
        p = new halfturn_up;
        (*(halfturn_up *)p).Radius(arg1);
        break;
      case QUARTERTURN_UP:
        p = new quarterturn_up;
        (*(quarterturn_up *)p).Radius(arg1);
        break;
      case UTURN_UP:
        p = new uturn_up;
        (*(uturn_up *)p).Radius(arg1);
        break;
      case HALFTURN_DOWN:
        p = new halfturn_down;
        (*(halfturn_down *)p).Radius(arg1);
        break;
      case QUARTERTURN_DOWN:
        p = new quarterturn_down;
        (*(quarterturn_down *)p).Radius(arg1);
        break;
      case UTURN_DOWN:
        p = new uturn_down;
        (*(uturn_down *)p).Radius(arg1);
        break;
      case ASCEND:
        p = new ascend;
        (*(ascend *)p).Radius(arg1);
        break;
      case DESCEND:
        p = new descend;
        (*(descend *)p).Radius(arg1);
        break;
      case ASCEND_HIGH:
        p = new ascend_high;
        (*(ascend_high *)p).Radius(arg1);
        break;
      case DESCEND_HIGH:
        p = new descend_high;
        (*(descend_high *)p).Radius(arg1);
        break;
      case ASCEND_LOW:
        p = new ascend_low;
        (*(ascend_low *)p).Radius(arg1);
        break;
      case DESCEND_LOW:
        p = new descend_low;
        (*(descend_low *)p).Radius(arg1);
        break;
      case FULLWHIRL:
        p = new fullwhirl;
        (*(fullwhirl *)p).Length(arg1);
        break;
      case FULLWHIRL_CLOCKWISE:
        p = new fullwhirl_clockwise;
        (*(fullwhirl_clockwise *)p).Length(arg1);
        break;
      case HALFWHIRL:
        p = new halfwhirl;
        (*(halfwhirl *)p).Length(arg1);
        break;
      case HALFWHIRL_CLOCKWISE:
        p = new halfwhirl_clockwise;
        (*(halfwhirl_clockwise *)p).Length(arg1);
        break;
      case DOUBLEWHIRL:
        p = new doublewhirl;
        (*(doublewhirl *)p).Length(arg1);
        break;
      case DOUBLEWHIRL_CLOCKWISE:
        p = new doublewhirl_clockwise;
        (*(doublewhirl_clockwise *)p).Length(arg1);
        break;
      case LOOPING:
        p = new looping;
        (*(looping *)p).Radius(arg1);
        break;
      default:
        return;
    }
    p->flag = flag;
    AddTrack(p);
  }

  RenderableEntities* Construct() {
    if(construction)
      return construction;
    construction = new RenderableEntities;
    if(parts.size()==0)
      return construction;

    RenderableEntities tubes;
    join_tracks();
    info = new track_info[parts.size()];
    n_tracks = parts.size();
    tid = 0;
    track_length = 0;
    track_base *ptrack;
    double l = 0;
    parts.rewind();
    for(int i=0; i<parts.size(); i++) {
      ptrack = *parts;
      construction->Add(ptrack->rails());
      tubes.Add(ptrack->tube());
      info[i].track = ptrack;
      info[i].length = ptrack->length();
      info[i].before = track_length;
      track_length+=ptrack->length();
      ++parts;
    }
    construction->Add(tubes);
    return construction;
  }

  RenderableEntities* Construct(int n, train_track_desc *desc) {
    for(int i=n; i; i--, desc++)
      AddTrack(desc->type, desc->arg1, desc->arg2, desc->f);
    return Construct();
  }

  GLmatrix coordinate_system(double len) {
    double l = len - floor(len/track_length)*track_length;
    while(l<info[tid].before||l>(info[tid].before+info[tid].length)) 
      if(l<info[tid].before) tid--;
      else tid++;
    l = (l-info[tid].before)/info[tid].length; // scale to <0,1>
    GLmatrix_double q = (info[tid].track)->coordinate_system(l);
    GLmatrix m;
    for(int i=0; i<16; i++)
      m[i] = (GLfloat)q[i];
    return m;
  }
  quaternion<float> orientation(double len) {
    double l = len - floor(len/track_length)*track_length;
    while(l<info[tid].before||l>(info[tid].before+info[tid].length)) 
      if(l<info[tid].before) tid--;
      else tid++;
    l = (l-info[tid].before)/info[tid].length; // scale to <0,1>
    quaternion<double> q = (info[tid].track)->orientation(l);
    return quaternion<float> ((float)re(q), (float)im1(q), (float)im2(q), (float)im3(q));
  }

  void Render() {
    if(construction)
      construction->Render();
  }

};


#endif