/****************************************************************************

   Copyright (C) 2002-2008 Gilles Debunne. All rights reserved.

   This file is part of the QGLViewer library version 2.3.5.

   http://www.libqglviewer.com - contact@libqglviewer.com

   This file may be used under the terms of the GNU General Public License
   versions 2.0 or 3.0 as published by the Free Software Foundation and
   appearing in the LICENSE file included in the packaging of this file.
   In addition, as a special exception, Gilles Debunne gives you certain
   additional rights, described in the file GPL_EXCEPTION in this package.

   libQGLViewer uses dual licensing. Commercial/proprietary software must
   purchase a libQGLViewer Commercial License.

   This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
   WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

 *****************************************************************************/

#include "drawer.h"
#include <QGLViewer/qglviewer.h>
#include <qimage.h>
#include <qstringlist.h>
#include <qmessagebox.h>

using namespace std;
using namespace dvonn;
using namespace qglviewer;

namespace {
static const float whiteColor[3] = { 1.0f, 1.0f, 1.0f };
static const float blackColor[3] = { 0.2f, 0.2f, 0.2f };
static const float redColor[3] = { 1.0f, 0.0f, 0.0f };
static const float boardColor[3] = { 1.0f, 1.0f, 1.0f };
static const float boardBorderColor[3] = { 0.0f, 0.0f, 0.0f };
// Dimension (D=widht=height) of a case and of the inner border of the
// case (B)
const float caseD = 1.0f;
const float caseB = 0.2f;
// Dimension of a piece
const float pieceRMax = (caseD - 2.0f * caseB) / sqrtf(2.0f);
const float pieceRMin = pieceRMax / 3.0f;
const float pieceH = 0.14f;
const float pieceC = 0.25f; // curvature of normal on vring part
const float pieceT = 0.5f;  // piece transparence
// Dimension of the label case
float hLabelW = caseD; // must be caseD
float hLabelH = caseD / 3.0f;
float vLabelW = caseD / 3.0f;
float vLabelH = caseD; // must be caseD
// Dimension of the board
const float boardB = 0.1f;
const float boardW = Board::nbSpacesMaxOnRow() * caseD + 2 * boardB + 2 * vLabelW;
const float boardH = Board::nbRows() * caseD + 2 * boardB + 2 * hLabelH;
const float poolB = 0.1f;
void tessAndTexture(float x0, float y0, float x2, float y2, float u0 = 0.0f, float v0 = 0.0f, float u2 = 1.0f,
                    float v2 = 1.0f)
{
  if(x0 > x2)
    swap(x0, x2);
  if(y0 > y2)
    swap(y0, y2);
  if(x2 - x0 > caseD) {
    float x1 = (x0 + x2) / 2.0f;
    float u1 = (u0 + u2) / 2.0f;
    tessAndTexture(x0, y0, x1, y2, u0, v0, u1, v2);
    tessAndTexture(x1, y0, x2, y2, u1, v0, u2, v2);
  } else if(y2 - y0 > caseD) {
    float y1 = (y0 + y2) / 2.0f;
    float v1 = (v0 + v2) / 2.0f;
    tessAndTexture(x0, y0, x2, y1, u0, v0, u2, v1);
    tessAndTexture(x0, y1, x2, y2, u0, v1, u2, v2);
  } else {
    glMultiTexCoord2f(GL_TEXTURE0, x0 / caseD, y0 / caseD);
    glMultiTexCoord2f(GL_TEXTURE1, u0, v0);
    glVertex2f(x0, y0);
    glMultiTexCoord2f(GL_TEXTURE0, x2 / caseD, y0 / caseD);
    glMultiTexCoord2f(GL_TEXTURE1, u2, v0);
    glVertex2f(x2, y0);
    glMultiTexCoord2f(GL_TEXTURE0, x2 / caseD, y2 / caseD);
    glMultiTexCoord2f(GL_TEXTURE1, u2, v2);
    glVertex2f(x2, y2);
    glMultiTexCoord2f(GL_TEXTURE0, x0 / caseD, y2 / caseD);
    glMultiTexCoord2f(GL_TEXTURE1, u0, v2);
    glVertex2f(x0, y2);
  }
}
const Vec normal[5]
  = { Vec(-1.0, 0.0, 0.0), Vec(0.0, 1.0, 0.0), Vec(1.0, 0.0, 0.0), Vec(0.0, -1.0, 0.0), Vec(-1.0, 0.0, 0.0) };
void drawHRing(const float rMin, const float rMax, const float height)
{
  // TODO : tabulate the cos and sin tables
  const int nbSteps = 24;

  glNormal3f(0.0, 0.0, 1.0);
  glBegin(GL_QUAD_STRIP);
  glVertex3f(rMin, 0.0, height);
  glVertex3f(rMax, 0.0, height);
  for(int i = 1; i <= nbSteps; ++i) {
    const float angle = i * 2.0 * M_PI / nbSteps;
    const float cosine = cos(angle);
    const float sine = sin(angle);
    glVertex3f(rMin * cosine, rMin * sine, height);
    glVertex3f(rMax * cosine, rMax * sine, height);
  }
  glEnd();
}
void drawVRing(const float hMin, const float hMax, const float r, bool in)
{
  const int nbSteps = 24;
  const float normalSign = (in ? -1.0 : 1.0);
  float thMin = hMin;
  float thMax = hMax;
  if(in)
    swap(thMax, thMin);
  glBegin(GL_QUAD_STRIP);
  glNormal3f(normalSign, 0.0, 0.0);
  for(int i = 0; i <= nbSteps; ++i) {
    const float angle = i * 2.0 * M_PI / nbSteps;
    const float cosine = cos(angle);
    const float sine = sin(angle);
    glNormal3fv(Vec(normalSign * cosine, normalSign * sine, +pieceC).unit());
    glVertex3f(r * cosine, r * sine, thMax);
    glNormal3fv(Vec(normalSign * cosine, normalSign * sine, -pieceC).unit());
    glVertex3f(r * cosine, r * sine, thMin);
  }
  glEnd();
}
void drawPiece(const Color& p, float a = 1.0f)
{
  static const float* colors[3] = { redColor, whiteColor, blackColor };
  const float* c = colors[static_cast<int>(p)];
  glColor4f(c[0] * a, c[1] * a, c[2] * a, a);
  drawHRing(pieceRMin, pieceRMax, pieceH);
  drawVRing(0.0f, pieceH, pieceRMax, false);
  drawVRing(0.0f, pieceH, pieceRMin, true);
}
void drawHLabel(unsigned int i)
{
  glBegin(GL_QUADS);
  unsigned int d = Board::nbRows() / 2;
  if(i < Board::nbSpacesMaxOnRow() - d)   // Bottom
  {
    float x = boardB + vLabelW + (d + 1) * (caseD / 2.0f) + i * hLabelW;
    float y = boardB;
    tessAndTexture(x, y, x + hLabelH, y + hLabelH, 0.0f, 0.0f, 1.0f, 1.0f);
    tessAndTexture(x + hLabelH, y, x + hLabelW, y + hLabelH, 2.0f, 2.0f, 2.0f, 2.0f);
  }
  if(i >= d) {
    float x = boardB + vLabelW - (d + 1) * (caseD / 2.0f) + i * hLabelW;
    float y = boardB + hLabelH + Board::nbRows() * caseD;
    tessAndTexture(x, y, x + hLabelW - hLabelH, y + hLabelH, 2.0f, 2.0f, 2.0f, 2.0f);
    tessAndTexture(x + hLabelW - hLabelH, y, x + hLabelW, y + hLabelH, 0.0f, 0.0f, 1.0f, 1.0f);
  }
  glEnd();
}
void drawVLabel(int i)
{
  glBegin(GL_QUADS);
  int d = static_cast<int>(Board::nbRows() / 2);
  int e = abs(d - i);
  float shift = e * caseD / 2.0f;
  float x = boardB + shift;
  float y = boardB + hLabelH + i * vLabelH;
  tessAndTexture(x, y, x + vLabelW, y + vLabelH / 3.0f, 2.0f, 2.0f, 2.0f, 2.0f);
  tessAndTexture(x, y + vLabelH / 3.0f, x + vLabelW, y + 2.0f * vLabelH / 3.0f, 0.0f, 0.0f, 1.0f, 1.0f);
  tessAndTexture(x, y + 2.0f * vLabelH / 3.0f, x + vLabelW, y + vLabelH, 2.0f, 2.0f, 2.0f, 2.0f);
  x = boardB + shift + vLabelW + (Board::nbSpacesMaxOnRow() - e) * caseD;
  y = boardB + hLabelH + i * vLabelH;
  tessAndTexture(x, y, x + vLabelW, y + vLabelH / 3.0f, 2.0f, 2.0f, 2.0f, 2.0f);
  tessAndTexture(x, y + vLabelH / 3.0f, x + vLabelW, y + 2.0f * vLabelH / 3.0f, 0.0f, 0.0f, 1.0f, 1.0f);
  tessAndTexture(x, y + 2.0f * vLabelH / 3.0f, x + vLabelW, y + vLabelH, 2.0f, 2.0f, 2.0f, 2.0f);
  glEnd();
}
GLuint loadTexture(const QString fileName)
{
  QImage img("images/" + fileName);
  if(img.isNull()) {
    QMessageBox::critical(NULL, "Image not found", "Unable to load texture from " + fileName);
    exit(1);     // TODO: be nicer!
  }
  // 1E-3 needed. Just try with width=128 and see !
  if((img.width() != 1 << (int)(1 + log(img.width() - 1 + 1E-3) / log(2.0)))
     || (img.height() != 1 << (int)(1 + log(img.height() - 1 + 1E-3) / log(2.0)))) {
    QMessageBox::critical(NULL, "Wrong image dimensions", "Texture dimensions are not powers of 2 in " + fileName);
    exit(1);     // TODO: be nicer!
  }
  // cout << "Loaded "<<fileName<<endl;
  QImage glImg = QGLWidget::convertToGLFormat(img);   // flipped 32bit RGBA
  // Create a texture object
  GLuint to;
  glGenTextures(1, &to);
  glBindTexture(GL_TEXTURE_2D, to);
  gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, glImg.width(), glImg.height(), GL_RGBA, GL_UNSIGNED_BYTE, glImg.bits());
  return to;
}
}
//************************************************************
// Implementation of Drawer
//************************************************************
Drawer::Drawer()
  : showTextures_(true)
{
}
void Drawer::toggleTexture(bool b) {
  showTextures_ = b;
}
Drawer::~Drawer() {
}
void Drawer::init()
{
  static const QStringList texFilenames = QStringList() << "board.png"
                                                        << "case.png";
  for(QStringList::const_iterator it = texFilenames.begin(); it != texFilenames.end(); ++it) {
    textures_[*it] = loadTexture(*it);
  }
  for(unsigned int i = 0; i < Board::nbSpacesMaxOnRow(); ++i) {
    static const char* letter = "ABCDEFGHIJK";
    hLabels_.push_back(loadTexture(QString("label%1.png").arg(letter[i])));
  }
  for(unsigned int i = 0; i < Board::nbRows(); ++i) {
    vLabels_.push_back(loadTexture(QString("label%1.png").arg(i + 1)));
  }
  glActiveTexture(GL_TEXTURE0);
  glEnable(GL_TEXTURE_2D);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glActiveTexture(GL_TEXTURE1);
  glEnable(GL_TEXTURE_2D);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
void Drawer::startTexture(const QString& t) const
{
  glPushAttrib(GL_ENABLE_BIT);
  glActiveTexture(GL_TEXTURE0);
  map<QString, GLuint>::const_iterator fter = textures_.find(t);
  if(showTextures_ && fter != textures_.end()) {
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, fter->second);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  } else {
    glDisable(GL_TEXTURE_2D);
  }
  glActiveTexture(GL_TEXTURE1);
  glDisable(GL_TEXTURE_2D);
}
void Drawer::startTexture(const QString& t, GLuint to) const
{
  glPushAttrib(GL_ENABLE_BIT);
  glActiveTexture(GL_TEXTURE0);
  map<QString, GLuint>::const_iterator fter = textures_.find(t);
  if(showTextures_ && fter != textures_.end()) {
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, fter->second);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  } else {
    glDisable(GL_TEXTURE_2D);
  }
  glActiveTexture(GL_TEXTURE1);
  if(showTextures_) {
    glBindTexture(GL_TEXTURE_2D, to);
    glEnable(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  } else {
    glDisable(GL_TEXTURE_2D);
  }
}
void Drawer::startTexture() const
{
  glPushAttrib(GL_ENABLE_BIT);
  glActiveTexture(GL_TEXTURE0);
  glDisable(GL_TEXTURE_2D);
  glActiveTexture(GL_TEXTURE1);
  glDisable(GL_TEXTURE_2D);
}
void Drawer::endTexture() const {
  glPopAttrib();
}
void translateTo(Board::Coord c, float h = 0.0f)
{
  static const float shifts[5] = { 1.0f, 0.5f, 0.0f, -0.5f, -1.0f };
  glTranslatef(boardB + vLabelW + c.x() * caseD + shifts[c.y()] * caseD, boardB + hLabelH + c.y() * caseD, h);
}
void Drawer::drawPieces(const Board::ConstStackHandle& s) const
{
  startTexture();
  glPushMatrix();
  translateTo(s.stackCoord());
  glTranslatef(0.5f * caseD, 0.5f * caseD, 0.0f);
  for(Stack::const_iterator iter = s->begin(), istop = s->end(); iter != istop; ++iter) {
    drawPiece((*iter)->color());
    glTranslatef(0.0f, 0.0f, pieceH);
  }
  glPopMatrix();
  endTexture();
}
void Drawer::drawStatus(const Board::ConstStackHandle& s, QGLViewer* v) const
{
  startTexture();
  glPushMatrix();
  translateTo(s.stackCoord(), s->height() * pieceH + pieceH / 2.0f);
  glColor3f(1.0f, 1.0f, 0.0f);
  v->renderText(0.5f * caseD, 0.5f * caseD, 0.0f, QString("%1").arg(s.stackStatus()));
  glPopMatrix();
  endTexture();
}
void Drawer::drawTransparentPiece(Color col, const Board::ConstStackHandle& c, float a) const
{
  startTexture();
  glPushMatrix();
  translateTo(c.stackCoord(), c->height() * pieceH);
  glTranslatef(0.5f * caseD, 0.5f * caseD, 0.0f);
  drawPiece(col, a);
  glPopMatrix();
  endTexture();
}
void Drawer::drawTransparentPieces(Stack::const_iterator first, Stack::const_iterator last, const Board::Coord& c,
                                   float h, float a) const
{
  startTexture();
  glPushMatrix();
  translateTo(c, h * pieceH);
  glTranslatef(0.5f * caseD, 0.5f * caseD, 0.0f);
  while(first != last) {
    drawPiece((*first++)->color(), a);
    glTranslatef(0.0f, 0.0f, pieceH);
  }
  glPopMatrix();
  endTexture();
}
void Drawer::draw(const Board::ConstStackHandle& c) const
{
  glPushMatrix();
  translateTo(c.stackCoord());
  startTexture("case.png");
  glColor3fv(boardColor);
  glNormal3fv(boardUpVector());
  glBegin(GL_QUADS);
  tessAndTexture(0.0f, 0.0f, caseD, caseD);
  glEnd();
  endTexture();
  glPopMatrix();
}
void Drawer::drawComplement(bool showLabels) const
{
  glPushAttrib(GL_ALL_ATTRIB_BITS);
  glNormal3fv(boardUpVector());
  glColor3fv(boardColor);
  for(unsigned int i = 0; i < Board::nbSpacesMaxOnRow(); ++i) {
    if(showLabels)
      startTexture("board.png", hLabels_[i]);
    else
      startTexture("board.png");
    drawHLabel(i);
    endTexture();
  }
  glColor3fv(boardColor);
  for(unsigned int i = 0; i < Board::nbRows(); ++i) {
    if(showLabels)
      startTexture("board.png", vLabels_[i]);
    else
      startTexture("board.png");
    drawVLabel(i);
    endTexture();
  }
  startTexture("board.png");
  glBegin(GL_QUADS);
  if(boardB > 0.0f) {
    glColor3fv(boardBorderColor);
    tessAndTexture(0.0f, 0.0f, boardW, boardB);
    tessAndTexture(0.0f, boardH - boardB, boardW, boardH);
    tessAndTexture(0.0f, boardB, boardB, boardH - boardB);
    tessAndTexture(boardW - boardB, boardB, boardW, boardH - boardB);
  }
  glColor3fv(boardColor);
  int d = Board::nbRows() / 2;
  tessAndTexture(boardB, boardB, boardB + vLabelW + (d + 1) * caseD / 2.0f, boardB + hLabelH);
  tessAndTexture(boardB, boardH - boardB - hLabelH, boardB + vLabelW + (d + 1) * caseD / 2.0f - hLabelW,
                 boardH - boardB);
  tessAndTexture(boardW - (boardB + vLabelW + (d + 1) * caseD / 2.0f) + hLabelW, boardB, boardW - boardB,
                 boardB + hLabelH);
  tessAndTexture(boardW - (boardB + vLabelW + (d + 1) * caseD / 2.0f), boardH - boardB - hLabelH, boardW - boardB,
                 boardH - boardB);
  for(int i = 0; i < d; ++i) {
    tessAndTexture(boardB, boardB + hLabelH + i * vLabelH, boardB + (d - i) * caseD / 2.0f,
                   boardB + hLabelH + i * vLabelH + vLabelH);
    tessAndTexture(boardB, boardH - (boardB + hLabelH + i * vLabelH + vLabelH), boardB + (d - i) * caseD / 2.0f,
                   boardH - (boardB + hLabelH + i * vLabelH));
    tessAndTexture(boardW - (boardB + (d - i) * caseD / 2.0f), boardB + hLabelH + i * vLabelH, boardW - boardB,
                   boardB + hLabelH + i * vLabelH + vLabelH);
    tessAndTexture(boardW - (boardB + (d - i) * caseD / 2.0f), boardH - (boardB + hLabelH + i * vLabelH + vLabelH),
                   boardW - boardB, boardH - (boardB + hLabelH + i * vLabelH));
  }
  glEnd();
  endTexture();
  glPopAttrib();
}
void Drawer::drawWhitePiecePools(const Board& b, bool lastTransparent) const
{
  unsigned int d = Board::nbRows() / 2;
  unsigned int e = Board::nbSpacesMaxOnRow() - d;
  startTexture();
  // Draw the whites and its up to two reds
  unsigned int nbR = b.nbUnplacedPieces(Red);
  unsigned int nbW = b.nbUnplacedPieces(White);
  unsigned int nbRed4White = nbR / 2 + nbR % 2;
  for(unsigned int istop = nbW + nbRed4White, i = 0; i < istop; ++i) {
    const float x = boardB + vLabelW + (d + 1) * caseD / 2.0f + (i % e) * caseD;
    static const float y = -poolB - caseD / 2.0f;
    const float z = (i / e) * pieceH;
    glPushMatrix();
    glTranslatef(x, y, z);
    drawPiece((i < nbW) ? White : Red, (lastTransparent && i == nbW + nbRed4White - 1) ? 0.5f : 1.0f);
    glPopMatrix();
  }
  endTexture();
}
void Drawer::drawBlackPiecePools(const Board& b, bool lastTransparent) const
{
  unsigned int d = Board::nbRows() / 2;
  unsigned int e = Board::nbSpacesMaxOnRow() - d;
  startTexture();
  // Draw the blacks and its up to one red
  unsigned int nbR = b.nbUnplacedPieces(Red);
  unsigned int nbB = b.nbUnplacedPieces(Black);
  unsigned int nbRed4Black = nbR / 2;
  for(unsigned int istop = nbB + nbRed4Black, i = 0; i < istop; ++i) {
    const float x = boardW - (boardB + vLabelW + (d + 1) * caseD / 2.0f + (i % e) * caseD);
    static const float y = boardH + poolB + caseD / 2.0f;
    const float z = (i / e) * pieceH;
    glPushMatrix();
    glTranslatef(x, y, z);
    drawPiece((i < nbB) ? Black : Red, (lastTransparent && i == nbB + nbRed4Black - 1) ? 0.4f : 1.0f);
    glPopMatrix();
  }
  endTexture();
}
void Drawer::highlight(const Board::ConstStackHandle& c) const
{
  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_LIGHTING);
  startTexture();
  glPushMatrix();
  translateTo(c.stackCoord(), 0.05f * pieceH + pieceH / 2.0f);
  glScalef(caseD, caseD, pieceH);
  // glutWireCube(1.0f);
  glPopMatrix();
  endTexture();
  glPopAttrib();
}
void Drawer::highlightPieces(const Board::ConstStackHandle& c) const
{
  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_LIGHTING);
  startTexture();
  glPushMatrix();
  translateTo(c.stackCoord(), 0.01f);
  glTranslatef(0.5f * caseD, 0.5f * caseD, 0.0f);
  glColor4f(0.0f, 0.0f, 0.3f, 0.3f);
  drawHRing(pieceRMin, 1.2f * pieceRMax, 0.0f);
  glPopMatrix();
  endTexture();
  glPopAttrib();
}
void Drawer::drawMove(const Board& b, const Game::Move m, float t) const
{
  const float L = caseD * estimateDrawMoveLength(b, m);
  const float t0 = (b.heightMax() + 1 - b.stackAt(m.src)->height()) * pieceH / L;
  const float t1 = 1.0f - (b.heightMax() + 1 - b.stackAt(m.dst)->height()) * pieceH / L;
  Board::ConstStackHandle src = b.stackAt(m.src);
  glPushMatrix();
  startTexture();
  if(t <= t0) {
    t /= t0;
    float srcH = src->height() * pieceH;
    float dstH = b.heightMax() * pieceH + pieceH;
    translateTo(m.src);
    glTranslatef(0.5f * caseD, 0.5f * caseD, (1 - t) * srcH + t * dstH);
    for(Stack::const_iterator iter = src->begin(), istop = src->end(); iter != istop; ++iter) {
      drawPiece((*iter)->color());
      glTranslatef(0.0f, 0.0f, pieceH);
    }
  } else if(t <= t1) {
    t = (t - t0) / (t1 - t0);
    static const float shifts[5] = { 1.0f, 0.5f, 0.0f, -0.5f, -1.0f };
    float srcX = boardB + vLabelW + m.src.x() * caseD + shifts[m.src.y()] * caseD;
    float srcY = boardB + hLabelH + m.src.y() * caseD;
    float dstX = boardB + vLabelW + m.dst.x() * caseD + shifts[m.dst.y()] * caseD;
    float dstY = boardB + hLabelH + m.dst.y() * caseD;
    glTranslatef((1.0f - t) * srcX + t * dstX + 0.5f * caseD, (1.0f - t) * srcY + t * dstY + 0.5f * caseD,
                 b.heightMax() * pieceH + pieceH);
    for(Stack::const_iterator iter = src->begin(), istop = src->end(); iter != istop; ++iter) {
      drawPiece((*iter)->color());
      glTranslatef(0.0f, 0.0f, pieceH);
    }
  } else {
    t = (t - t1) / (1.0f - t1);
    float srcH = b.heightMax() * pieceH + pieceH;
    float dstH = b.stackAt(m.dst)->height() * pieceH;
    translateTo(m.dst);
    glTranslatef(0.5f * caseD, 0.5f * caseD, (1 - t) * srcH + t * dstH);
    for(Stack::const_iterator iter = src->begin(), istop = src->end(); iter != istop; ++iter) {
      drawPiece((*iter)->color());
      glTranslatef(0.0f, 0.0f, pieceH);
    }
  }
  endTexture();
  glPopMatrix();
}
float Drawer::estimateDrawMoveLength(const Board& b, const Game::Move m) const
{
  static const float shifts[5] = { 1.0f, 0.5f, 0.0f, -0.5f, -1.0f };
  float srcX = boardB + vLabelW + m.src.x() * caseD + shifts[m.src.y()] * caseD;
  float srcY = boardB + hLabelH + m.src.y() * caseD;
  float dstX = boardB + vLabelW + m.dst.x() * caseD + shifts[m.dst.y()] * caseD;
  float dstY = boardB + hLabelH + m.dst.y() * caseD;
  return (Vec(dstX - srcX, dstY - srcY, 0.0f).norm() + (b.heightMax() + 1 - b.stackAt(m.src)->height()) * pieceH
          + (b.heightMax() + 1 - b.stackAt(m.dst)->height()) * pieceH) / caseD;
}
Vec Drawer::boardCenter() const {
  return Vec(boardW / 2.0f, boardH / 2.0f, 0.0f);
}
Vec Drawer::boardUpVector() const {
  return Vec(0.0f, 0.0f, 1.0f);
}
float Drawer::boardRadius() const {
  return Vec(boardW / 2.0f, boardH / 2.0f, 0.0).norm();
}
Vec Drawer::defaultEyePosition() const {
  return boardCenter() + boardRadius() * Vec(0.0, -1.7f, 1.0);
}
