/* $Id: draw.cc,v 1.8 2000/01/13 20:11:22 mac Exp $ */

/*
 * glbiff -- A Mesa/OpenGL-based `xbiff' substitute
 * Copyright (C) 2000  Maciej Kalisiak
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include <time.h>
#include <iostream.h>

#include <GL/gl.h>
#include <GL/glx.h>

#include "draw.h"
#include "glbiff.h"
#include "astro.h"
#include "rgb.h"
#include "cfg.h"

// some angles (in degrees)
const int ANG_FLAG_UP = 90;
const int ANG_FLAG_DOWN = 0;
const int ANG_DOOR_OPEN = 90;
const int ANG_DOOR_CLOSED = 0;
const int ANG_CAM_INI = 0;		// INIt = "tilted/corner" view
const int ANG_CAM_FIN = 1;		// FInal = head on box view

// some step sizes
const double STEP_FLAG = (ANG_FLAG_UP-ANG_FLAG_DOWN)
  /(double)flag_up_frames;
const double STEP_DOOR = (ANG_DOOR_OPEN-ANG_DOOR_CLOSED)
  / (double)door_open_frames;
const double STEP_CAM = (ANG_CAM_FIN-ANG_CAM_INI)
  / (double)cam_swing_frames;

////////// globals
double flag_position=0;
double door_position=0;
double xform_factor=0;

bool fDoorOpen = false;
bool fLookHeadOn = false;

/*
 * set_colour()
 *
 * generic routine to set current colour; uses some reasonable default
 * values. Requires that you pass in the RGB of the desired colour.
 */
void set_colour(float r, float g, float b)
{
  float ambient = 0.2;
  float diffuse = 0.7;
  float specular = 0.4;
  GLfloat mat[4];
  /**** set ambient lighting parameters ****/
  mat[0] = ambient*r;
  mat[1] = ambient*g;
  mat[2] = ambient*b;
  mat[3] = 1.0;
  glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, mat);

  /**** set diffuse lighting parameters ******/
  mat[0] = diffuse*r;
  mat[1] = diffuse*g;
  mat[2] = diffuse*b;
  mat[3] = 1.0;
  glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, mat);

  /**** set specular lighting parameters *****/
  mat[0] = specular*r;
  mat[1] = specular*g;
  mat[2] = specular*b;
  mat[3] = 1.0;
  glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, mat);
  glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 0.5);
}

void draw_cube( double d )
{
  double v = 0.5 * d;

  // NOTE: the spurious braces are here so that XEmacs does nice indentation...
  glBegin(GL_QUAD_STRIP); {
    glVertex3f(-v,-v,v);
    glVertex3f(-v,-v,-v);
    glVertex3f(v,-v,v);
    glVertex3f(v,-v,-v);
    glVertex3f(v,v,v);
    glVertex3f(v,v,-v);
    glVertex3f(-v,v,v);
    glVertex3f(-v,v,-v);
    glVertex3f(-v,-v,v);
    glVertex3f(-v,-v,-v);
  }
  glEnd();

  glBegin(GL_QUADS); {
    glVertex3f(-v,-v,v);	// front face
    glVertex3f(v,-v,v);
    glVertex3f(v,v,v);
    glVertex3f(-v,v,v);

    glVertex3f(-v,v,-v);	// back face
    glVertex3f(v,v,-v);
    glVertex3f(v,-v,-v);
    glVertex3f(-v,-v,-v);
  }
  glEnd();
}

void outlined_cube( double d )
{
  draw_cube( d );
  set_colour(0,0,0);

  double v = 0.5 * d;
  
  glBegin(GL_LINE_LOOP); {
    glVertex3f(-v,-v,-v);
    glVertex3f(v,-v,-v);
    glVertex3f(v,v,-v);
    glVertex3f(-v,v,-v);
  }
  glEnd();

  glBegin(GL_LINE_LOOP); {
    glVertex3f(-v,-v,v);
    glVertex3f(v,-v,v);
    glVertex3f(v,v,v);
    glVertex3f(-v,v,v);
  }
  glEnd();

  glBegin(GL_LINES); {
    glVertex3f(-v,-v,-v);
    glVertex3f(-v,-v,v);
    glVertex3f(-v,v,-v);
    glVertex3f(-v,v,v);
    glVertex3f(v,v,-v);
    glVertex3f(v,v,v);
    glVertex3f(v,-v,-v);
    glVertex3f(v,-v,v);
  }
  glEnd();
}

/*
 * draw_mbox_top()
 *
 * draws the Bezier patch which makes the top of mailbox
 */
void draw_mbox_top() {
  glEnable(GL_MAP2_VERTEX_3);
  glEvalMesh2(GL_FILL, 0, DOME_SEGS, 0, DOME_SEGS);
  glDisable(GL_MAP2_VERTEX_3);
}

/*
 * draw_mbox_side()
 *
 * draw a single side of a mailbox; (nx,ny,nz) is the normal vector for
 * the side.
 */
void draw_mbox_side( float nx, float ny, float nz ) {
  glBegin(GL_POLYGON);
    glNormal3f(nx,ny,nz);
    glVertex3f(0,0,0);
    glVertex3f(0,0,SIDE_HEIGHT);
    glVertex3f(0,1,SIDE_HEIGHT);
    glVertex3f(0,1,0);
  glEnd();
}

/*
 * draw_mbox_door()
 *
 * draws the door, using a Bezier curve, among other things. This is used 
 * both for the front, and the back doors.
 */
void draw_mbox_door() {
  //  glTranslatef(-0.5,-0.5*1.6,0);
  glEnable(GL_MAP1_VERTEX_3);
  glPushMatrix();
  glTranslatef(0,0,SIDE_HEIGHT);
  glBegin(GL_POLYGON);
    glNormal3f(0,-1,0);
    glVertex3f(0,0,-SIDE_HEIGHT);
    for( int i=0; i<=DOME_SEGS; i++ )
      glEvalCoord1f(((float)i)/((GLfloat)DOME_SEGS));
    glVertex3f(1,0,-SIDE_HEIGHT);
  glEnd();
  glPopMatrix();
  glDisable(GL_MAP1_VERTEX_3);
}

/*
 * draw_mbox_flag()
 *
 * draws the flag, taking 'flag_position' into account.
 */
void draw_mbox_flag() {
  glPushMatrix();
  glScalef(FLAG_SCALE,FLAG_SCALE,FLAG_SCALE*1.3);
  glRotatef(90-flag_position,0,1,0);
  glTranslatef(0.05,0,0.5);

  glPushMatrix();
  glScalef(0.1,0.05,1);
  draw_cube(1.0);
  glPopMatrix();

  glPushMatrix();
  glTranslatef(0.2,0,0.35);
  glScalef(0.3,0.05,0.3);
  draw_cube(1.0);
  glPopMatrix();

  glPopMatrix();
}

/*
 * draw_env()
 *
 * draws a single small envelope: stands for 1 email
 */
void draw_env() {
  set_colour(1,1,1);
  glPushMatrix();
  glRotatef(-10,0,1,0);
  glTranslatef(0.01,0.5+0.1,0.2);
  glScalef(0.02,1,0.4);
  outlined_cube(1.0);
  glPopMatrix();
}

/*
 * draw_big_env()
 *
 * draws a bigger envelope: stands for 5 emails
 */
void draw_big_env() {
  set_colour(0.8,0.6,0);
  glPushMatrix();
  glRotatef(-5,0,1,0);
  glTranslatef(0.01,0.5+0.1,0.25);
  glScalef(0.02,1,0.5);
  outlined_cube(1.0);
  glPopMatrix();
};

/*
 * draw_box()
 *
 * draws a small package: stands for 25 emails
 */
void draw_box() {
  set_colour(0.4,0.3,0.1);
  glPushMatrix();
  glTranslatef(0.2,0.5+0.1,0.25);
  glScalef(0.4,1,0.5);
  outlined_cube(1.0);
  glPopMatrix();
};

/*
 * draw_big_box()
 *
 * draws a big package: stands for 50 emails
 */
void draw_big_box() {
  set_colour(0.5,0.5,0.1);
  glPushMatrix();
  glTranslatef(0.2,0.5+0.1,0.37);
  glScalef(0.4,1,0.74);
  outlined_cube(1.0);
  glPopMatrix();
};

/*
 * draw_mail()
 *
 * draws the representation of 'mail_count' emails in the mailbox
 */
void draw_mail() {
  // determine number of each object
  int bbox, box, benv, env;

  if( mail_count>=MAX_MAIL_DRAWABLE ) {
    bbox=2;
    box=benv=env=0;
  } else {
    int mc=mail_count;

    bbox=mc/50;
    mc-=bbox*50;

    box=mc/25;
    mc-=box*25;

    benv=mc/5;
    mc-=benv*5;

    env=mc;
  }

  bool fLL, fLR, fUL, fUR;
  fLL=fLR=fUL=fUR=true;

  if( bbox ) {
    glPushMatrix();
    glTranslatef(0.09,0,0);
    draw_big_box();
    glPopMatrix();
    fLL=fUL=false;

    if( bbox==2 ) {
      glPushMatrix();
      glTranslatef(0.51,0,0);
      draw_big_box();
      glPopMatrix();
      fLR=fUR=false;
    }
  }

  if( box ) {
    glPushMatrix();
    if( fLL ) {
      glTranslatef(0.02,0,0);
      fLL=false;
    } else {
      glTranslatef(0.52,0,0);
      fLR=false;
    }
    draw_box();
    glPopMatrix();
  }

  if( benv || env ) {
    glPushMatrix();
    if( fLL ) {
      fLL=false;
    } else if( fLR ) {
      glTranslatef(0.5,0,0);
      fLR=false;
    } else if( fUR ) {
      glTranslatef(0.5,0,0.5);
      fUR=false;
    }

    for( int i=0; i<benv; i++ ) {
      glTranslatef( 0.05,0,0 );
      draw_big_env();
    }
    for( int i=0; i<env; i++ ) {
      glTranslatef( 0.05,0,0 );
      draw_env();
    }

    glPopMatrix();
  }
}

/*
 * draw_mbox()
 *
 * draws the complete mailbox; post, flag and contained email included
 */
void draw_mbox() {
  set_colour(0.5,0.5,0.5);

  /* scale the whole box, w/o flag or front door */
  glPushMatrix();
  glTranslatef(-0.5,-0.5*1.6,0);	// center the box along x and y
  glScalef(1,1.6,1);

  /* draw the top */
  glPushMatrix();
  glTranslatef(0,0,SIDE_HEIGHT);
  draw_mbox_top();
  glPopMatrix();

  draw_mbox_side( -1, 0, 0 );

  glPushMatrix();
  glTranslatef(1,0,0);
  draw_mbox_side( 1, 0, 0 );
  glPopMatrix();

  /* draw the bottom */
  glBegin(GL_POLYGON);
    glNormal3f(0,0,1);
    glVertex3f(0,0,0);
    glVertex3f(1,0,0);
    glVertex3f(1,1,0);
    glVertex3f(0,1,0);
  glEnd();

  // draw the back door
  glPushMatrix();
  glTranslatef(0,1,0);
  draw_mbox_door();
  glPopMatrix();

  glPopMatrix();		/* end of box w/o flag, front door xform */
    
  // draw the front door
  glPushMatrix();
  glTranslatef(-0.5,-0.5*1.6,0);	// center the box along x and y
  glRotatef(door_position,1,0,0);
  draw_mbox_door();
  glPopMatrix();

  // draw the flag in red
  set_colour(1.0,0,0);
  glPushMatrix();
  glTranslatef(-0.5,-0.5*1.6,0);	// center the box along x and y
  glScalef(1.0,1.2,1.0);
  glTranslatef(1.1,0.2,0.5);
  glRotatef(90,0,0,1);
  draw_mbox_flag();
  glPopMatrix();

  // draw the mail count
  glPushMatrix();
  glTranslatef(-0.5,-0.5*1.6,0);	// center the box along x and y
  draw_mail();
  glPopMatrix();  
}


/*
 * draw_land()
 *
 * draws the square which represents the ground
 */
void draw_land() {
  // texture stuff (enable)
  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glBindTexture(GL_TEXTURE_2D, texName);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);	// correct perspective

  glPushMatrix();
  glRotatef( 45, 0, 0, 1 );
  set_colour(0.0,0.6,0.0);
  glBegin(GL_POLYGON);
    glNormal3f(0,0,1);
    glTexCoord2f(0,0); glVertex3f(-LAND_WIDTH/2,-LAND_WIDTH/2,-3);
    glTexCoord2f(0,3); glVertex3f(-LAND_WIDTH/2,LAND_WIDTH/2,-3);
    glTexCoord2f(3,3); glVertex3f(LAND_WIDTH/2,LAND_WIDTH/2,-3);
    glTexCoord2f(3,0); glVertex3f(LAND_WIDTH/2,-LAND_WIDTH/2,-3);
  glEnd();

  glPopMatrix();
  
  glFlush();
  glDisable(GL_TEXTURE_2D);
}

/*
 * draw_post()
 *
 * draws the post on which the mailbox is sitting
 */
void draw_post() {
  set_colour(0.4,0.4,0);

  glPushMatrix();
  glTranslatef(-0.5,-0.5*1.6,0); // the whole box moved to be centered on x,y
  glTranslatef(0.5,0.5*1.6,-1.51);
  glScalef(0.2,0.2,3);
  draw_cube(1.0);
  glPopMatrix();
}

void set_sky_colour(void) {
  // get time and date
  time_t tt = time( NULL );
  tm* ptm = localtime( &tt );
  Date today(ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday);
  double hour = ptm->tm_hour + ptm->tm_min/60.0 + ptm->tm_sec/3600.0;

//   static double hour = 7;
//   hour += 0.05;

//   if( hour > 24 )
//     hour -= 24;
  

  
  coord rs = sun_rise_set( today, my_long_lat );

//   cerr << "rs == " << rs.a << ", " << rs.b << endl;
//   cerr << "time is " << ptm->tm_hour << ":" << ptm->tm_min << ":"
//        << ptm->tm_sec << "    ; tm_isdst == " << ptm->tm_isdst << endl;

//   cerr << "hour is " << hour << endl;
  
  if( hour <= rs.a || hour >= rs.b )
    glClearColor(0,0,0,1.0);		// pitch dark
  else {
    double s = (hour - rs.a)/(rs.b-rs.a);
    list<Rgb_kf>::iterator it = sky_keyframes.begin();
    list<Rgb_kf>::iterator it_after;
    while( (*it).s < s )
      ++it;

    it_after = it;
    --it;
    
    double f = 1 - (s - (*it).s)/
      ((*it_after).s-(*it).s);

//     cerr << "i == " << i << "  : s == " << s << "  : f == " << f << endl;
    
    glClearColor( (*it).rgb.r*f + (*it_after).rgb.r*(1-f),
		  (*it).rgb.g*f + (*it_after).rgb.g*(1-f),
		  (*it).rgb.b*f + (*it_after).rgb.b*(1-f),
		  1.0 );
  }
}


/*
 * redraw()
 *
 * this is the callback that handles drawing of the OpenGL window
 */
void redraw( Display *dpy, Window w )
{
  if( !dpy || !w )
    return;
  
  bool call_me_again = false;

  // "paint" the sky
  set_sky_colour();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // setup the viewing transformation (assume the models are already setup)
  glPushMatrix();

  // --camera/viewing transformations
  // read these backwards using the camera local coordinate system
  // (or forwards using the "Grand, Fixed Coordinate System")
  glTranslatef( 0,0,-3.0+1.0*xform_factor );	// dolly out a bit
  glRotatef( -90+35-35.0*xform_factor,1,0,0 );
  glRotatef( -45+45*xform_factor,0,0,1 );	// controls rot along z
  glTranslatef( 0,0,-0.25-0.25*xform_factor );	// move camera to the vertical center of box
  
//   glScalef(2,2,2);

  // paint the origin as a cube
//   draw_cube(0.1);
  
  /////// perform any motions necessary for this frame
  // check the flag, ...   
  if( unreadmail && flag_position<ANG_FLAG_UP ) {
    flag_position += STEP_FLAG;
    call_me_again = true;
  }
  if( !unreadmail && flag_position>ANG_FLAG_DOWN ) {
    flag_position -= STEP_FLAG;
    call_me_again = true;
  }
  // door, ...
  if( fDoorOpen && door_position<ANG_DOOR_OPEN ) {
    door_position += STEP_DOOR;
    call_me_again = true;
  }
  if( !fDoorOpen && door_position>ANG_DOOR_CLOSED ) {
    door_position -= STEP_DOOR;
    call_me_again = true;
  }
  // and camera swing
  if( fLookHeadOn && xform_factor<ANG_CAM_FIN ) {
    xform_factor += STEP_CAM;
    call_me_again = true;
  }
  if( !fLookHeadOn && xform_factor>ANG_CAM_INI ) {
    xform_factor -= STEP_CAM;
    call_me_again = true;
  }

  // render the land and mailbox
  draw_land();
  draw_post();
  draw_mbox();

  // clean up and swap buffers
  glPopMatrix();
  glFinish();
  glXSwapBuffers(dpy, w);

  // setup a callback if any motions are not yet finished
  if(call_me_again) {
    timed_callback( refresh, refresh_period );
  }
}


////////////////////////////////////////////////////////////
// texturing stuff

GLubyte check_image[check_image_h][check_image_w][4];
GLuint texName;

void make_check_image(void) {
  int i, j, c;
  for( i=0; i<check_image_h; i++ ) {
    for( j=0; j<check_image_w; j++ ) {
      c = ((((i&0x8)==0)^((j&0x8))==0))*55+200;
      check_image[i][j][0] = c;
      check_image[i][j][1] = c;      
      check_image[i][j][2] = c;
      check_image[i][j][3] = 255;
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1