/*!
  \file
  \brief OpenGL での描画処理

  「GLUT による OpenGL 入門」(床井 浩平) を参考にした

  \author Satofumi KAMIMURA

  $Id: gl_draw.cpp 1268 2009-08-30 22:11:41Z satofumi $
*/

#include "gl_draw.h"
#include "Color.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <map>

using namespace std;


namespace
{
  enum {
    X = 0,
    Y = 1,
    Z = 2,
  };


  typedef map<dBodyID, GLUquadricObj*> Spheres;
  Spheres spheres_;
}


void qrk::gl_loadIdentity(void)
{
  glLoadIdentity();
}


void qrk::gl_moved(dBodyID id)
{
  const dReal* position = dBodyGetPosition(id);
  glTranslated(position[X], position[Y], position[Z]);

  const dReal* rotation = dBodyGetRotation(id);
  GLdouble matrix[16];
  matrix[0] = rotation[0];
  matrix[1] = rotation[4];
  matrix[2] = rotation[8];
  matrix[3] = 0.0;
  matrix[4] = rotation[1];
  matrix[5] = rotation[5];
  matrix[6] = rotation[9];
  matrix[7] = 0.0;
  matrix[8] = rotation[2];
  matrix[9] = rotation[6];
  matrix[10] = rotation[10];
  matrix[11] = 0.0;
  matrix[12] = rotation[3];
  matrix[13] = rotation[7];
  matrix[14] = rotation[11];
  matrix[15] = 1.0;

  glMultMatrixd(matrix);
}


void qrk::gl_setColor(const Color& color)
{
  GLfloat draw_color[4];
  draw_color[0] = color.r;
  draw_color[1] = color.g;
  draw_color[2] = color.b;
  draw_color[3] = color.a;
  glMaterialfv(GL_FRONT, GL_DIFFUSE, draw_color);
}


void qrk::gl_drawBox(const dReal length[], bool wireframe)
{
  GLdouble vertex[8][3];
  vertex[0][0] = -length[X] / 2.0;
  vertex[0][1] = -length[Y] / 2.0;
  vertex[0][2] = -length[Z] / 2.0;
  vertex[1][0] = +length[X] / 2.0;
  vertex[1][1] = -length[Y] / 2.0;
  vertex[1][2] = -length[Z] / 2.0;
  vertex[2][0] = +length[X] / 2.0;
  vertex[2][1] = +length[Y] / 2.0;
  vertex[2][2] = -length[Z] / 2.0;
  vertex[3][0] = -length[X] / 2.0;
  vertex[3][1] = +length[Y] / 2.0;
  vertex[3][2] = -length[Z] / 2.0;

  vertex[4][0] = -length[X] / 2.0;
  vertex[4][1] = -length[Y] / 2.0;
  vertex[4][2] = +length[Z] / 2.0;
  vertex[5][0] = +length[X] / 2.0;
  vertex[5][1] = -length[Y] / 2.0;
  vertex[5][2] = +length[Z] / 2.0;
  vertex[6][0] = +length[X] / 2.0;
  vertex[6][1] = +length[Y] / 2.0;
  vertex[6][2] = +length[Z] / 2.0;
  vertex[7][0] = -length[X] / 2.0;
  vertex[7][1] = +length[Y] / 2.0;
  vertex[7][2] = +length[Z] / 2.0;

  int face[6][4];
  face[0][0] = 0; face[0][1] = 1; face[0][2] = 2; face[0][3] = 3;
  face[1][0] = 1; face[1][1] = 5; face[1][2] = 6; face[1][3] = 2;
  face[2][0] = 5; face[2][1] = 4; face[2][2] = 7; face[2][3] = 6;
  face[3][0] = 4; face[3][1] = 0; face[3][2] = 3; face[3][3] = 7;
  face[4][0] = 4; face[4][1] = 5; face[4][2] = 1; face[4][3] = 0;
  face[5][0] = 3; face[5][1] = 2; face[5][2] = 6; face[5][3] = 7;

  GLdouble normal[6][3];
  normal[0][0] = +0.0; normal[0][1] = +0.0; normal[0][2] = -1.0;
  normal[1][0] = +1.0; normal[1][1] = +0.0; normal[1][2] = +0.0;
  normal[2][0] = +0.0; normal[2][1] = +0.0; normal[2][2] = +1.0;
  normal[3][0] = -1.0; normal[3][1] = +0.0; normal[3][2] = +0.0;
  normal[4][0] = +0.0; normal[4][1] = -1.0; normal[4][2] = +0.0;
  normal[5][0] = +0.0; normal[5][1] = +1.0; normal[5][2] = +0.0;

  for (int i = 0; i < 6; ++i) {
    if (! wireframe) {
      glBegin(GL_QUADS);
    } else {
      glBegin(GL_LINE_LOOP);
    }
    glNormal3dv(normal[i]);
    for (int j = 4 - 1; j >= 0; --j) {
      glVertex3dv(vertex[face[i][j]]);
    }
    glEnd();
  }
}


void qrk::gl_drawCylinder(const dReal length[], bool wireframe)
{
  enum { Side = 32 };
  const double radius = length[0];
  const double height = length[1] / 2.0;
  const double step = 2.0 * M_PI / Side;

  // 上の面
  glNormal3d(0.0, 0.0, 1.0);
  if (! wireframe) {
    glBegin(GL_TRIANGLE_FAN);
  } else {
    glBegin(GL_LINE_LOOP);
  }
  for (int i = 0; i < Side; ++i) {
    double radian = step * i;
    glVertex3d(radius * cos(radian), radius * sin(radian), height);
  }
  glEnd();

  // 下の面
  glNormal3d(0.0, 0.0, -1.0);
  if (! wireframe) {
    glBegin(GL_TRIANGLE_FAN);
  } else {
    glBegin(GL_LINE_LOOP);
  }
  for (int i = Side - 1; i >= 0; --i) {
    double radian = step * i;
    glVertex3d(radius * cos(radian), radius * sin(radian), -height);
  }
  glEnd();

  // 側面
  if (! wireframe) {
    glBegin(GL_QUAD_STRIP);
  } else {
    glBegin(GL_LINES);
  }
  for (int i = 0; i <= Side; ++i) {
    double radian = step * i;
    double x = cos(radian);
    double y = sin(radian);

    glNormal3d(x, y, 0.0);
    glVertex3d(radius * x, radius * y, height);
    glVertex3d(radius * x, radius * y, -height);
  }
  glEnd();
}


void qrk::gl_drawSphere(dBodyID id, const dReal length[], bool wireframe)
{
  (void)wireframe;

  const double radius = length[0];

  // !!! オブジェクトの削除方法を検討すべき

  Spheres::iterator it = spheres_.find(id);
  if (it == spheres_.end()) {
    GLUquadricObj* sphere = gluNewQuadric();
    gluQuadricDrawStyle(sphere, GLU_FILL);
    spheres_[id] = sphere;
  }

  GLUquadricObj* sphere = spheres_[id];
  gluSphere(sphere, radius, 20, 20);
}


void qrk::gl_drawGround(void)
{
  GLfloat color[][4] = {
    { 0.6, 0.6, 0.6, 1.0 },
    { 0.4, 0.4, 0.4, 1.0 },
  };
  double height = 0.0;

  // !!! 視点中心から、一定範囲までを描画するようにする

  glNormal3d(0.0, 0.0, 1.0);
  const int draw_m = 10;
  for (int i = -draw_m; i < draw_m; ++i) {
    for (int j = -draw_m; j < draw_m; ++j) {
      glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color[(i + j) & 0x1]);

      glBegin(GL_QUADS);
      glVertex3d(j, i + 1, height);
      glVertex3d(j, i, height);
      glVertex3d(j + 1, i, height);
      glVertex3d(j + 1, i + 1, height);
      glEnd();
    }
  }
}
