/// LSU EE 4702-1 (Fall 2009), GPU Programming -*- c++ -*-
//
 /// Quick-and-Dirty Routines for Drawing some OpenGL Shapes

// $Id:$

#ifndef SHAPES_H
#define SHAPES_H

class Sphere {
public:
  Sphere(){};
  void init(int slices);
  void render();
  void render(pVect position){ center = position; render(); }
  void render(pVect position, pVect axisp, double anglep)
  {
    center = position; axis = axisp;  angle = anglep;
    rotation_matrix_compute();
    render();
  }
  void rotation_matrix_compute();
  int slices;
  pBuffer_Object<pVect> points_bo;
  pBuffer_Object<float> tex_coord_bo;
  PStack<int> strip_starts;
  PStack<int> strip_sizes;
  pCoor center;
  pVect axis, axis_prepared;
  double angle, angle_prepared;
  float radius;
  pMatrix rotation_matrix;
  bool default_orientation;
};

void
Sphere::init(int slicesp)
{
  slices = slicesp;
  axis = pVect(0,1,0);
  angle = 0;
  radius = 2;
  default_orientation = true;
  const double two_pi = 2.0 * M_PI;
  const double delta_theta = two_pi / slices;
  const double epsilon = 0.001 * delta_theta;
  const double pi_me = M_PI - epsilon;
  const double two_pi_me = two_pi - epsilon;
  PStack<pVect> points;
  PStack<float> tex_coord;
  for ( double theta = 0; theta < two_pi_me; theta += delta_theta )
    {
      const int point_count = points.occ();
      strip_starts += point_count;
      const double theta1 = theta + delta_theta;
      const double cos_th0 = cos(theta);
      const double sin_th0 = sin(theta);
      const double cos_th1 = cos(theta1);
      const double sin_th1 = sin(theta1);
      const float tc_s0 = theta / two_pi;
      const float tc_s1 = theta1 / two_pi;
      points += pVect(0,1,0);
      tex_coord += 1-(tc_s0 + tc_s1) * 0.5;  tex_coord += 0;
      for ( double eta = delta_theta; eta < pi_me; eta += delta_theta )
        {
          const float y = cos(eta);
          const double slice_r = sin(eta);
          const float x0 = slice_r * cos_th0;
          const float z0 = slice_r * sin_th0;
          const float tc_t = eta / M_PI;
          points += pVect(slice_r * cos_th1, y, slice_r * sin_th1);
          tex_coord += 1-tc_s1;  tex_coord += tc_t;
          points += pVect(x0,y,z0);
          tex_coord += 1-tc_s0;  tex_coord += tc_t;
        }
      points += pVect(0,-1,0);
      tex_coord += 1-(tc_s0 + tc_s1) * 0.5;  tex_coord += 1;
      strip_sizes += points.occ() - point_count;
    }
  points_bo.take(points);
  points_bo.to_gpu();
  tex_coord_bo.take(tex_coord);
  tex_coord_bo.to_gpu();
}

void
Sphere::rotation_matrix_compute()
{
  pVect up(0,1,0);
  default_orientation = axis == up && angle == 0;
  if ( default_orientation ) return;
  if ( angle == angle_prepared && axis == axis_prepared ) return;
  pMatrix_Rotation orient_axis(up,axis);
  pMatrix_Rotation rotate(up,angle);
  rotation_matrix = orient_axis * rotate;
  angle_prepared = angle;  axis_prepared = axis;
}

void
Sphere::render()
{
  const pColor lsu_spirit_gold(0xf9b237);
  glColor3fv(lsu_spirit_gold);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glTranslatef(center.x,center.y,center.z);
  glScalef(radius,radius,radius);
  if ( !default_orientation ) glMultTransposeMatrixf(rotation_matrix);
  points_bo.bind();
  glVertexPointer(3,GL_FLOAT,0,0);
  glEnableClientState(GL_VERTEX_ARRAY);
  glNormalPointer(GL_FLOAT,0,0);
  glEnableClientState(GL_NORMAL_ARRAY);
  tex_coord_bo.bind();
  glTexCoordPointer(2,GL_FLOAT,0,0);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glMultiDrawArrays
    (GL_TRIANGLE_STRIP,
     strip_starts.get_storage(), strip_sizes.get_storage(), slices);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glPopMatrix();
}


class Cone {
public:
  Cone(){};
  void render(pCoor base, float radius, pVect to_apex)
  {
    const int sides = 10;
    const double delta_theta = 2 * M_PI / sides;
    const double base_radius = 1;
    const double apex_radius = 0.1;
    const double apex_height = 1;
    const double alpha = atan2(apex_height,base_radius-apex_radius);
    const double vec_z = sin(alpha);
    const float to_height = to_apex.mag();

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    pVect from_apex(0,0,1);
    pVect rn(from_apex,to_apex);
    const float rot_angle = pangle(from_apex,to_apex);
    glTranslatef(base.x,base.y,base.z);
    glRotatef(rot_angle * 180.0 / M_PI,rn.x,rn.y,rn.z);
    glScalef(radius,radius,to_height);
    glBegin(GL_QUAD_STRIP);
    for ( int i=0; i<=sides; i++ )
      {
        const double theta = delta_theta * i;
        const double cos_t = cos(theta);
        const double sin_t = sin(theta);
        glNormal3f( cos_t, sin_t, vec_z );
        glVertex3f( apex_radius * cos_t, apex_radius * sin_t, apex_height);
        glVertex3f( base_radius * cos_t, base_radius * sin_t, 0);
      }
    glEnd();
    glPopMatrix();
  }
};

// Display a tetrahedron, used to indicate light position.
//
inline void
insert_tetrahedron(pCoor& loc, float size)
{
  pCoor v0(loc.x,loc.y,loc.z);
  pCoor v1(loc.x,loc.y-size,loc.z+size);
  pCoor v2(loc.x-.866*size,loc.y-size,loc.z-0.5*size);
  pCoor v3(loc.x+.866*size,loc.y-size,loc.z-0.5*size);
  static pColor c1(0xffffff);
  static pColor c2(0xff00);

  glDisable(GL_LIGHTING);

#define TRI(va,vb,vc)                                                         \
  {                                                                           \
    pVect n = cross(va,vb,vc);                                                \
    glNormal3fv(n);                                                           \
    glColor3fv(c1);  glVertex3fv(va);                                         \
    glColor3fv(c2);  glVertex3fv(vb);                                         \
    glVertex3fv(vc);                                                          \
  }

  glBegin(GL_TRIANGLES);
  TRI(v0,v1,v2); TRI(v0,v2,v3); TRI(v0,v3,v1);
  glEnd();

# undef TRI

  glEnable(GL_LIGHTING);
}

#endif