/*
 * logo.C
 *
 * Demo of the gltt graphics library
 * Copyright (C) 1998 Stephane Rehel
 *
 * January 1998
 */

#include <assert.h>

#include "FTEngine.h"
#include "FTFace.h"
#include "FTInstance.h"
#include "FTGlyph.h"
#include "FTGlyphBitmap.h"
#include "FTBitmapFont.h"
#include "GLTTBitmapFont.h"
#include "GLTTOutlineFont.h"
#include "GLTTFont.h"
#include "GLTTGlyphPolygonizer.h"
#include "GLTTGlyphTriangulator.h"

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <gltk.h>

#include "hsv_to_rgb.h"

/////////////////////////////////////////////////////////////////////////////

static char* font_name= 0;
static FTFace* face= 0;
static int width= 640;
static int height= 9*width/16;
static double center_x= 0.;
static double center_y= 0.;
static double phi= 0.;
static double theta= 0.;
static double extrude_length= 30.;
static double rot_text_speed= 1.;
static double rot_text_freq= 1./300.;
static double camera_dist= 500.;
static const char* text= "OpenGL!";

/////////////////////////////////////////////////////////////////////////////

static void fatal( const char* msg )
{
  if( msg != 0 )
    fprintf( stderr, "%s\n", msg );

  exit(1);
}

/////////////////////////////////////////////////////////////////////////////

static void gl_swap()
{
  tkSwapBuffers();
}

/////////////////////////////////////////////////////////////////////////////

static void spheric_camera( float center_x, float center_y, float center_z,
                            float phi, float theta, float radius )
{
  float x= center_x + cos(phi) * cos(theta) * radius;
  float y= center_y + sin(phi) * cos(theta) * radius;
  float z= center_z +            sin(theta) * radius;

  float vx= -cos(phi) * sin(theta) * radius;
  float vy= -sin(phi) * sin(theta) * radius;
  float vz=             cos(theta) * radius;

  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective( 60, GLfloat(width)/GLfloat(height), 10, 10000 );

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt( x, y, z, center_x, center_y, center_z, vx, vy, vz );
}

/////////////////////////////////////////////////////////////////////////////

class GLTTGlyphTriangles: public GLTTGlyphTriangulator
{
public:
  struct Triangle
    {
    FTGlyphVectorizer::POINT* p1;
    FTGlyphVectorizer::POINT* p2;
    FTGlyphVectorizer::POINT* p3;
    };

  Triangle* triangles;
  int nTriangles;

  GLTTboolean count_them;

  GLTTGlyphTriangles( FTGlyphVectorizer* vectorizer ):
    GLTTGlyphTriangulator(vectorizer)
    {
    triangles= 0;
    nTriangles= 0;
    count_them= GLTT_TRUE;
    }
  virtual ~GLTTGlyphTriangles()
    {
    delete triangles;
    triangles= 0;
    }
  void alloc()
    {
    delete triangles;
    triangles= new Triangle [ nTriangles + 1 ];
    }
  virtual void triangle( FTGlyphVectorizer::POINT* p1,
                         FTGlyphVectorizer::POINT* p2,
                         FTGlyphVectorizer::POINT* p3 )
    {
    if( count_them )
      {
      ++nTriangles;
      return;
      }
    triangles[nTriangles].p1= p1;
    triangles[nTriangles].p2= p2;
    triangles[nTriangles].p3= p3;
    ++nTriangles;
    }
};

/////////////////////////////////////////////////////////////////////////////

static void draw()
{
  const int text_length= strlen(text);

  GLTTFont font(face);

  if( ! font.create(220) )
    return;

  FTGlyphVectorizer* vec= new FTGlyphVectorizer [ text_length ];
  GLTTGlyphTriangles** tri= new GLTTGlyphTriangles* [ text_length ];

  int i;
  for( i= 0; i < text_length; ++i )
    tri[i]= new GLTTGlyphTriangles( vec + i );

  double min_y= 1e20;
  double max_y=-1e20;
  double size_x= 0.;
  for( i= 0; i < text_length; ++i )
    {
    int ch= (unsigned char)text[i];

    FTGlyph* g= font.getFont()->getGlyph(ch);
    if( g == 0 )
      continue;
    FTGlyphVectorizer& v= vec[i];
    v.setPrecision(14.);
    if( ! v.init(g) )
      continue;

    size_x+= v.getAdvance();

    if( ! v.vectorize() )
      continue;

    for( int c= 0; c < v.getNContours(); ++c )
      {
      FTGlyphVectorizer::Contour* contour= v.getContour(c);
      if( contour == 0 )
        continue;
      for( int j= 0; j < contour->nPoints; ++j )
        {
        FTGlyphVectorizer::POINT* point= contour->points + j;
/*
disgusting
        double wave_t= 0.;
        double u= (point->x / 50.+ wave_t ) * M_PI * 2.;
        double v= (point->y / 50. + 1. + 2.*wave_t) * M_PI * 2.;
        point->x+= sin(u) * 5.;
        point->y+= sin(v) * 5.;
*/

        if( point->y < min_y ) min_y= point->y;
        if( point->y > max_y ) max_y= point->y;
        point->data= (void*) new double [ 6 ];
        }
      }

    GLTTGlyphTriangles* t= tri[i];

    if( ! t->init(g) )
      continue;

    t->count_them= GLTT_TRUE;
    t->nTriangles= 0;
    t->triangulate();

    t->count_them= GLTT_FALSE;
    t->alloc();
    t->nTriangles= 0;
    t->triangulate();
    }

  if( size_x == 0. )
    fatal("please give something to draw");

  double y_delta= (min_y+max_y)/2. + min_y + 50;
  for( i= 0; i < text_length; ++i )
    {
    FTGlyphVectorizer& v= vec[i];

    for( int c= 0; c < v.getNContours(); ++c )
      {
      FTGlyphVectorizer::Contour* contour= v.getContour(c);
      if( contour == 0 )
        continue;
      for( int j= 0; j < contour->nPoints; ++j )
        {
        FTGlyphVectorizer::POINT* point= contour->points + j;
        point->y -= y_delta;
        }
      }
    }

  float light1_ambient[4]= { 0.2,0.2,0.2,1 };
  float light1_diffuse[4]= { 0.9,0.9,0.9,1 };
  float light1_specular[4]= { 0.7,0.7,0.7,1 };
  float light1_position[4]= { -1,1,1,0 };
  glLightfv(GL_LIGHT1,GL_AMBIENT,light1_ambient);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,light1_diffuse);
  glLightfv(GL_LIGHT1,GL_SPECULAR,light1_specular);
  glLightfv(GL_LIGHT1,GL_POSITION,light1_position);
  glEnable(GL_LIGHT1);

  float light2_ambient[4]= { 0.1,0.1,0.1,1 };
  float light2_diffuse[4]= { 0.8,0.2,0.2,1 };
  float light2_specular[4]= { 0.5,0.5,0.5,1 };
  float light2_position[4]= { 1,-1,-1,0 };
  glLightfv(GL_LIGHT2,GL_AMBIENT,light2_ambient);
  glLightfv(GL_LIGHT2,GL_DIFFUSE,light2_diffuse);
  glLightfv(GL_LIGHT2,GL_SPECULAR,light2_specular);
  glLightfv(GL_LIGHT2,GL_POSITION,light2_position);
  glEnable(GL_LIGHT2);

  float front_emission[4]= { 0.,0.,0.,0 };
  float front_ambient[4]= { 0.2,0.2,0.2,0 };
  float front_diffuse[4]= { 0.95,0.95,0.8,0 };
  float  back_diffuse[4]= { 0.6,0.6,0.95,0 };
  float front_specular[4]= { 0.6,0.6,0.6,0 };
  glMaterialfv(GL_FRONT,GL_EMISSION,front_emission);
  glMaterialfv(GL_FRONT,GL_AMBIENT,front_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,front_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,front_specular);
  glMaterialf(GL_FRONT,GL_SHININESS,32.);

  float  back_color[4]= { 0.2,0.2,0.6,0 };
  glMaterialfv(GL_BACK,GL_DIFFUSE,back_color);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_FALSE);
  glCullFace(GL_BACK);
  glFrontFace(GL_CCW);
  glEnable(GL_CULL_FACE);

  glColorMaterial(GL_FRONT,GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  spheric_camera( center_x,
                  center_y + size_x/2.,
                  0,
                  phi, theta+M_PI/2, camera_dist );
  glClearColor(0,0,0,0);
  glEnable(GL_DEPTH_TEST);
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  glEnable(GL_LIGHTING);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_NORMALIZE);

  double base_x= 0.;
  for( i= 0; i < text_length; ++i )
    {
    FTGlyphVectorizer& v= vec[i];

    for( int c= 0; c < v.getNContours(); ++c )
      {
      FTGlyphVectorizer::Contour* contour= v.getContour(c);
      if( contour == 0 )
        continue;

      for( int j= 0; j < contour->nPoints; ++j )
        {
        FTGlyphVectorizer::POINT* point= contour->points + j;
        double cx= -point->y;
        double cy= base_x+point->x;
        double phi= sin(cy*rot_text_freq)* rot_text_speed * M_PI/2.;
        double rcx= cx * cos(phi);
        double rcz= cx * sin(phi);

        double* p= (double*) point->data;
        double* n= p + 3;

        p[0]= rcx;
        p[1]= cy;
        p[2]= rcz;

        n[0]= -sin(phi);
        n[1]= 0.;
        n[2]=  cos(phi);
        }
      }

    GLTTGlyphTriangles::Triangle* triangles= tri[i]->triangles;
    int nTriangles= tri[i]->nTriangles;

    glBegin(GL_TRIANGLES);

    for( int j= 0; j < nTriangles; ++j )
      {
      GLTTGlyphTriangles::Triangle& t= triangles[j];

      double* p1= ((double*) t.p1->data);
      double* p2= ((double*) t.p2->data);
      double* p3= ((double*) t.p3->data);
      double* n1= p1 + 3;
      double* n2= p2 + 3;
      double* n3= p3 + 3;

      glColor4fv(front_diffuse);

      glNormal3dv( n1 );
      glVertex3dv( p1 );
      glNormal3dv( n2 );
      glVertex3dv( p2 );
      glNormal3dv( n3 );
      glVertex3dv( p3 );

      glColor4fv(back_diffuse);

      glNormal3d( -n3[0], 0., -n3[2] );
      glVertex3d( p3[0]-n3[0]*extrude_length,
                  p3[1],
                  p3[2]-n3[2]*extrude_length );
      glNormal3d( -n2[0], 0., -n2[2] );
      glVertex3d( p2[0]-n2[0]*extrude_length,
                  p2[1],
                  p2[2]-n2[2]*extrude_length );
      glNormal3d( -n1[0], 0., -n1[2] );
      glVertex3d( p1[0]-n1[0]*extrude_length,
                  p1[1],
                  p1[2]-n1[2]*extrude_length );
      }
    glEnd();

    for( int c= 0; c < v.getNContours(); ++c )
      {
      FTGlyphVectorizer::Contour* contour= v.getContour(c);
      if( contour == 0 )
        continue;
      glBegin(GL_QUAD_STRIP);
      for( int j= 0; j <= contour->nPoints; ++j )
        {
        int j1= (j < contour->nPoints) ? j : 0;
        int j0= (j1==0) ? (contour->nPoints-1) : (j1-1);

        FTGlyphVectorizer::POINT* point0= contour->points + j0;
        FTGlyphVectorizer::POINT* point1= contour->points + j1;
        double* p0= (double*) point0->data;
        double* p1= (double*) point1->data;
        double* e= p0 + 3;
        double vx= p1[0] - p0[0];
        double vy= p1[1] - p0[1];
        double vz= p1[2] - p0[2];
        double nx=           - vy * e[2];
        double ny= e[2] * vx - vz * e[0];
        double nz= e[0] * vy ;

        double u= double( (j*2) % contour->nPoints )
                    / double(contour->nPoints);

        double r, g, b;
        hsv_to_rgb(u,0.7,0.7,&r,&g,&b);

        glColor4f(r,g,b,1); // diffuse color of material

        glNormal3f(nx,ny,nz);
        glVertex3f( p0[0]-e[0]*extrude_length,
                    p0[1],
                    p0[2]-e[2]*extrude_length );
        glNormal3f(nx,ny,nz);
        glVertex3f( p0[0], p0[1], p0[2] );
        }
      glEnd();
      }

    base_x += v.getAdvance();
    }

  glDisable(GL_NORMALIZE);
  glDisable(GL_LIGHTING);
  glShadeModel(GL_FLAT);
  glPopMatrix();

  gl_swap();

  for( i= 0; i < text_length; ++i )
    {
    delete tri[i];

    FTGlyphVectorizer& v= vec[i];
    for( int c= 0; c < v.getNContours(); ++c )
      {
      FTGlyphVectorizer::Contour* contour= v.getContour(c);
      if( contour == 0 )
        continue;
      for( int j= 0; j < contour->nPoints; ++j )
        {
        FTGlyphVectorizer::POINT* point= contour->points + j;
        delete point->data;
        point->data= 0;
        }
      }
    }

  delete tri;
  delete [] vec;
}

/////////////////////////////////////////////////////////////////////////////

static void reshape( int _width, int _height )
{
  width= _width;
  height= _height;
}

/////////////////////////////////////////////////////////////////////////////

static GLenum key( int k, GLenum /*mask*/ )
{
  const double angle_speed= 10./180.*M_PI;

  switch (k)
    {
    case TK_s:
       camera_dist /= 1.2;
       return GL_TRUE;
    case TK_S:
       camera_dist *= 1.2;
       return GL_TRUE;
    case TK_UP:
       theta+= angle_speed;
       return GL_TRUE;
    case TK_DOWN:
       theta-= angle_speed;
       return GL_TRUE;
    case TK_LEFT:
       phi-= angle_speed;
       return GL_TRUE;
    case TK_RIGHT:
       phi+= angle_speed;
       return GL_TRUE;
    case TK_e:
       extrude_length -= 5.0;
       return GL_TRUE;
    case TK_E:
       extrude_length += 5.0;
       return GL_TRUE;
    case TK_r:
       rot_text_speed /= 1.2;
       return GL_TRUE;
    case TK_R:
       rot_text_speed *= 1.2;
       return GL_TRUE;
    case TK_f:
       rot_text_freq *= 1.2;
       return GL_TRUE;
    case TK_F:
       rot_text_freq /= 1.2;
       return GL_TRUE;
    case TK_ESCAPE:
       tkQuit();
    }
  return GL_FALSE;
}

/////////////////////////////////////////////////////////////////////////////

int main( int argc, char *argv[] )
{
  if( argc != 3 )
    {
    fprintf( stderr, "%s: bad arguments.\nUsage:\n", argv[0] );
    fprintf( stderr, "\t%s <font_filename.ttf> \"text\"\n\n", argv[0] );
    fprintf( stderr, "Example:\n\t%s arial.ttf \"OpenGL\"\n\n", argv[0] );
    return 1;
    }

  font_name= argv[1];
  text= argv[2];

  FTFace bottomface;
  if( ! bottomface.open(font_name) )
    {
    fprintf( stderr, "unable to open font %s\n", font_name );
    return 1;
    }

  face= new FTFace;
  if( ! face->open(font_name) )
    {
    fprintf( stderr, "unable to open font %s\n", font_name );
    return 1;
    }

  tkInitPosition(0, 0, width, height);
  tkInitDisplayMode( GLenum(TK_RGB | TK_DEPTH | TK_DOUBLE | TK_DIRECT) );

  if( ! tkInitWindow("gltt demo") )
    {
    fatal( "unable to create main window");
    tkQuit();
    }

  tkExposeFunc( reshape );
  tkReshapeFunc( reshape );
  tkKeyDownFunc( key );
  tkDisplayFunc( draw );

  printf( "Key Usage:\n"
          "s(zoom in)  S(zoom out)\n"
          "left(dec camera phi) right(inc camera theta)\n"
          "up(inc camera theta) down(dec camera theta)\n"
          "e(dec extrusion)  E(inc extrusion)\n"
          "r(dec text rotation amplitude)  E(inc text.rot.ampl.)\n"
          "f(dec text rotation frequency)  E(inc text.rot.freq.)\n"
          "ESC(quit)\n" );

  tkExec();

  return 0;
}

/////////////////////////////////////////////////////////////////////////////
