/**************************************************************************** Copyright (C) 2002-2006 Gilles Debunne (Gilles.Debunne@imag.fr) This file is part of the QGLViewer library. Version 2.2.4-1, released on December 12, 2006. http://artis.imag.fr/Members/Gilles.Debunne/QGLViewer libQGLViewer 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. libQGLViewer 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 libQGLViewer; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ #include "dvonnviewer.h" #include "game.h" #include "drawer.h" #include #include #include using namespace std; using namespace qglviewer; using namespace dvonn; namespace { int debugInterval; const unsigned int nbAnimationSteps = 30; class BoardConstraint : public Constraint { public: virtual void constrainRotation(Quaternion& q, Frame * const fr) { const Vec up = fr->transformOf(Vec(0.0, 0.0, 1.0)); Vec axis = q.axis(); float angle = 2.0*acos(q[3]); if (fabs(axis*up) > fabs(axis.x)) axis.projectOnAxis(up); else { angle = (axis.x > 0.0) ? angle : -angle; axis.setValue(fabs(axis.x), 0.0, 0.0); const float currentAngle = asin(fr->inverseTransformOf(Vec(0.0, 0.0, -1.0)).z); if (currentAngle + angle > -0.2) angle = -0.2 - currentAngle; // Not too low if (currentAngle + angle < -M_PI/2.0) angle = -M_PI/2.0 - currentAngle; // Do not pass on the other side } q = Quaternion(axis, angle); } }; } //************************************************************ // Implementation of DvonnViewer //************************************************************ #if QT_VERSION < 0x040000 DvonnViewer::DvonnViewer(QWidget* parent, const char* name) : QGLViewer(parent, name), #else DvonnViewer::DvonnViewer(QWidget* parent) : QGLViewer(parent), #endif game_(NULL), selectionMode_(-1), piecePicked_(false), dstPicked_(Board::ConstStackHandle::null()), srcPicked_(Board::ConstStackHandle::null()), showPossDest_(true), showStatus_(false), showLabels_(false), useLight_(true), dragToPlay_(false), fadeGhosts_(NULL), animateT_(-1.0f), showAnimation_(true), scoreT_(-1.0f) { drawer_ = new Drawer; fadeTimer_ = new QTimer(); connect(fadeTimer_,SIGNAL(timeout()),this,SLOT(advanceFadeOut())); animateTimer_ = new QTimer(); connect(animateTimer_,SIGNAL(timeout()),this,SLOT(advanceAnimateMove())); scoreTimer_ = new QTimer(); connect(scoreTimer_,SIGNAL(timeout()),this,SLOT(advanceAnimateScore())); } DvonnViewer::~DvonnViewer() { delete drawer_; delete fadeTimer_; delete animateTimer_; delete scoreTimer_; } void DvonnViewer::advanceFadeOut() { fadeAlpha_ -= 0.05f; if (fadeAlpha_ < 0.0f) { fadeTimer_->stop(); fadeAlpha_ = 0.0f; fadeGhosts_ = NULL; } updateGL(); } void DvonnViewer::fadeOut(const Board::Ghosts* g) { if ((fadeGhosts_ = g) != NULL && !g->empty()) { fadeAlpha_ = 1.0f; fadeTimer_->start(30); } } void DvonnViewer::advanceAnimateMove() { animateT_ += 1.0f/nbAnimationSteps; if (animateT_ >= 1.0f) { animateTimer_->stop(); animateT_ = -1.0f; emit requested(animateMove_); } updateGL(); } void DvonnViewer::animateMove(Game::Move m) { if (animateT_ < 0.0f) { if (showAnimation_) { animateMove_ = m; animateT_ = 0.0f; static const float v = 1.0f/250; const float d = drawer_->estimateDrawMoveLength(game_->board(),m); const float T = d/v; animateTimer_->start(static_cast(T/nbAnimationSteps)); } else { emit requested(m); } } } void DvonnViewer::advanceAnimateScore() { scoreT_ += 1.0f/nbAnimationSteps; if (scoreT_ >= 1.0f) { scoreTimer_->stop(); scoreT_ = -1.0f; emit requested(scoreMove_); } updateGL(); } void DvonnViewer::animateScore() { if (scoreT_ < 0.0f) { // Stack will be moved to to positions at center of the board static const Board::Coord centers[2] = { Board::Coord(Board::nbSpacesMaxOnRow()/2,Board::nbRows()/2+1), Board::Coord(Board::nbSpacesMaxOnRow()/2+1,Board::nbRows()/2+1) }; // Search for a stack to move for (Board::ConstStackIterator iter = game_->board().stacks_begin(), istop= game_->board().stacks_end(); iter != istop;++iter) { if (iter.stackCoord() != centers[0] && iter.stackCoord() != centers[1] && iter->hasPieces()) { Color c = iter->onTop()->color(); if (c != Red) { scoreMove_ = Game::Move(iter.stackCoord(),centers[c-1]); scoreT_ = 0.0f; static const float v = 1.0f/250; const float d = drawer_->estimateDrawMoveLength(game_->board(), scoreMove_); const float T = d/v; scoreTimer_->start(debugInterval = static_cast(T/nbAnimationSteps)); return; } } } } } void DvonnViewer::stopAllAnimations() { fadeTimer_->stop(); fadeAlpha_ = 0.0f; fadeGhosts_ = NULL; animateTimer_->stop(); animateT_ = -1.0f; scoreTimer_->stop(); scoreT_ = -1.0f; updateGL(); } void DvonnViewer::setGame(Game* g) { game_ = g; } void DvonnViewer::toggleTexture(bool b) { drawer_->toggleTexture(b); updateGL(); } void DvonnViewer::toggleLight(bool b) { useLight_ = b; updateGL(); } void DvonnViewer::toggleShowPossible(bool b) { showPossDest_ = b; if (showPossDest_ && game_->phase() == MovePhase && !srcPicked_.isNull()) { possDests_ = game_->possibleDestinations(srcPicked_); } updateGL(); } void DvonnViewer::toggleShowStatus(bool b) { showStatus_ = b; updateGL(); } void DvonnViewer::toggleShowLabels(bool b) { showLabels_ = b; updateGL(); } void DvonnViewer::toggleShowAnimation(bool b) { showAnimation_ = b; updateGL(); } void DvonnViewer::toggleDragToPlay(bool b) { dragToPlay_ = b; updateGL(); } // I n i t i a l i z a t i o n f u n c t i o n s // void DvonnViewer::init() { initOpenGL(); initSpotLight(); initViewer(); drawer_->init(); } void DvonnViewer::initOpenGL() { glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); } void DvonnViewer::initSpotLight() { const GLfloat light_ambient[4] = {1.0, 1.0, 1.0, 1.0}; const GLfloat light_specular[4] = {1.0, 1.0, 1.0, 1.0}; const GLfloat light_diffuse[4] = {1.0, 1.0, 1.0, 1.0}; glLightf( GL_LIGHT1, GL_SPOT_EXPONENT, 2.0); glLightf( GL_LIGHT1, GL_SPOT_CUTOFF, 60.0); glLightf( GL_LIGHT1, GL_CONSTANT_ATTENUATION, 0.1); glLightf( GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.3); glLightf( GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.3); glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular); glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); const Vec pos = drawer_->boardCenter()+drawer_->boardRadius()*drawer_->boardUpVector(); const float posv[4] = { pos.x,pos.y,pos.z,1.0f }; const Vec dir = -drawer_->boardUpVector(); const float dirv[4] = { dir.x,dir.y,dir.z,1.0f }; glLightfv(GL_LIGHT1,GL_POSITION,posv); glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION,dirv); } void DvonnViewer::initViewer() { setSceneCenter(drawer_->boardCenter()); setSceneRadius(drawer_->boardRadius()); camera()->setUpVector(drawer_->boardUpVector()); camera()->setPosition(drawer_->defaultEyePosition()); camera()->lookAt(sceneCenter()); // Limit camera rotation motion camera()->frame()->setConstraint(new BoardConstraint()); #if QT_VERSION >= 0x040000 # define CONTROL Qt::ControlModifier # define SHIFT Qt::ShiftModifier #else # define CONTROL Qt::ControlButton # define SHIFT Qt::ShiftButton #endif // Defines new bindings setMouseBindingDescription(Qt::LeftButton, "Moves stack"); setMouseBinding(CONTROL | Qt::LeftButton,CAMERA,ROTATE); setMouseBinding(Qt::MidButton, ZOOM_TO_FIT,true); setMouseBinding(Qt::RightButton,ZOOM_TO_FIT,true); setMouseBinding(CONTROL | Qt::LeftButton,RAP_FROM_PIXEL,true); setMouseBinding(CONTROL | Qt::RightButton, RAP_IS_CENTER, true); // Disable most of the default bindings setMouseBinding(CONTROL | Qt::MidButton ,CAMERA,NO_MOUSE_ACTION); setMouseBinding(CONTROL | Qt::RightButton,CAMERA,NO_MOUSE_ACTION); setMouseBinding(Qt::LeftButton,NO_CLICK_ACTION); setMouseBinding(Qt::LeftButton,NO_CLICK_ACTION,true); setMouseBinding(CONTROL | Qt::LeftButton | Qt::MidButton,NO_CLICK_ACTION); setMouseBinding(CONTROL | Qt::RightButton | Qt::MidButton,NO_CLICK_ACTION); setMouseBinding(Qt::LeftButton | Qt::MidButton, NO_CLICK_ACTION); setMouseBinding(Qt::RightButton | Qt::MidButton, NO_CLICK_ACTION); setMouseBinding(SHIFT | Qt::LeftButton,NO_CLICK_ACTION); setMouseBinding(Qt::RightButton,NO_CLICK_ACTION, true, Qt::MidButton); setMouseBinding(Qt::LeftButton, NO_CLICK_ACTION, true, Qt::MidButton); setMouseBinding(Qt::RightButton,NO_CLICK_ACTION, true, Qt::LeftButton); setMouseBinding(Qt::LeftButton, NO_CLICK_ACTION, true, Qt::RightButton); setWheelBinding(CONTROL, FRAME,NO_MOUSE_ACTION); } void DvonnViewer::draw() { (useLight_?glEnable:glDisable)(GL_LIGHTING); glEnable(GL_LIGHT1); drawAllSpaces(); drawer_->drawComplement(showLabels_); drawAllPieces(); Player p = game_->theOnePlaying(); Color c = game_->phase() == RedPlacementPhase?Red:colorOf(p); drawer_->drawWhitePiecePools(game_->board(), p==WhitePlayer && piecePicked_); drawer_->drawBlackPiecePools(game_->board(), p==BlackPlayer && piecePicked_); if (piecePicked_ && !dstPicked_.isNull()) { drawer_->drawTransparentPiece(c,dstPicked_); } if (!piecePicked_ && !srcPicked_.isNull()) { drawer_->drawTransparentPieces(srcPicked_->begin(),srcPicked_->end(), srcPicked_.stackCoord(),0.0f,0.4f); if (showPossDest_) { glColor3f(1.0f,1.0f,0.0f); for (deque::const_iterator iter = possDests_.begin(); iter != possDests_.end();++iter) { drawer_->highlightPieces(*iter); } } if (!dstPicked_.isNull()) { drawer_->drawTransparentPieces(srcPicked_->begin(),srcPicked_->end(), dstPicked_.stackCoord(), dstPicked_->height(), 0.9f); } } // Ghosts if (fadeGhosts_) { for (Board::Ghosts::const_iterator iter = fadeGhosts_->begin(); iter != fadeGhosts_->end();++iter) { drawer_->drawTransparentPieces(iter->stack.begin(), iter->stack.end(), iter->coord, 0.0f, fadeAlpha_*fadeAlpha_); } } // Animated move if (animateT_>=0.0f) { drawer_->drawMove(game_->board(),animateMove_,animateT_); } if (scoreT_>=0.0f) { drawer_->drawMove(game_->board(),scoreMove_,scoreT_); } } void DvonnViewer::drawAllPieces(bool pick) { unsigned int name=0; for (Board::ConstStackIterator iter = game_->board().stacks_begin(), istop= game_->board().stacks_end(); iter != istop;++iter) { if (pick) glPushName(name++); if (srcPicked_ != iter && (animateT_ < 0.0f || iter.stackCoord() != animateMove_.src) && (scoreT_ < 0.0f || iter.stackCoord() != scoreMove_.src)) { drawer_->drawPieces(iter); } if (pick) glPopName(); } if (showStatus_ && !pick) { glPushAttrib(GL_ALL_ATTRIB_BITS); glDisable(GL_LIGHTING); glColor3f(0.0,1.0,0.0f); for (Board::ConstStackIterator iter = game_->board().stacks_begin(), istop= game_->board().stacks_end(); iter != istop;++iter) { drawer_->drawStatus(iter,this); } } glPopAttrib(); } void DvonnViewer::drawAllSpaces(bool pick) { unsigned int name=0; for (Board::ConstStackIterator iter = game_->board().stacks_begin(), istop= game_->board().stacks_end(); iter != istop;++iter) { if (pick) glPushName(name++); drawer_->draw(iter); if (pick) glPopName(); } } void DvonnViewer::drawWithNames() { if (game_->isOver()) return; switch (selectionMode_) { case 1: glPushName(0); if (game_->theOnePlaying() == WhitePlayer) { drawer_->drawWhitePiecePools(game_->board(),false); } else { drawer_->drawBlackPiecePools(game_->board(),false); } glPopName(); break; case 2: case 5: drawAllSpaces(true); break; case 3: case 4: drawAllPieces(true); break; default: cout<<"No selection mode active!"<board().stacks_begin(); for (int i=0;ihasPieces()) dstPicked_ = Board::ConstStackHandle::null(); } else { dstPicked_ = Board::ConstStackHandle::null(); } break; case 3: if (selectedName() != -1) { Board::ConstStackIterator iter = game_->board().stacks_begin(); for (int i=0;ionTop()->color() != colorOf(game_->theOnePlaying())) { srcPicked_ = Board::ConstStackHandle::null(); } } else { srcPicked_ = Board::ConstStackHandle::null(); } break; case 4: case 5: if (selectedName() != -1) { Board::ConstStackIterator iter = game_->board().stacks_begin(); for (int i=0;iisLegalMove(Game::Move(srcPicked_.stackCoord(), dstPicked_.stackCoord()))) { dstPicked_ = Board::ConstStackHandle::null(); } } else { dstPicked_ = Board::ConstStackHandle::null(); } break; }; selectionMode_ = -1; } void DvonnViewer::mousePressEvent(QMouseEvent* e) { #if QT_VERSION >= 0x040000 if (e->button() == Qt::LeftButton) #else if (e->stateAfter() == Qt::LeftButton) #endif { if (game_->phase() == RedPlacementPhase || game_->phase() == PiecePlacementPhase) { if (dragToPlay_) { selectionMode_ = 1; select(e); if (!dstPicked_.isNull()) { piecePicked_ = true; } } else { piecePicked_ = true;; Board::ConstStackHandle firstClickDstPicked = dstPicked_; selectionMode_ = 2; select(e); if (dstPicked_ == firstClickDstPicked) { commitDstPicked(); } else if (dstPicked_.isNull()) { piecePicked_ = false; } } updateGL(); } else // phase == MovePhase { if (dragToPlay_ || (!dragToPlay_ && srcPicked_.isNull())) { selectionMode_ = 3; select(e); // Check that the picked src is free if (!srcPicked_.isNull() && !game_->board().isFree(srcPicked_)) { srcPicked_ = Board::ConstStackHandle::null(); } // If asked, search for possible destinations if (showPossDest_ && !srcPicked_.isNull()) { possDests_ = game_->possibleDestinations(srcPicked_); } setMouseTracking(true); } else { selectionMode_ = 4; select(e); // Since selection in mode 4 can work only for case with pieces, we // try to select a case if no case is selected yet. if (dstPicked_.isNull()) { selectionMode_ = 5; select(e); } commitDstPicked(); } updateGL(); } } else QGLViewer::mousePressEvent(e); } void DvonnViewer::mouseMoveEvent(QMouseEvent* e) { #if QT_VERSION >= 0x040000 if ((dragToPlay_ && e->button() == Qt::LeftButton) || #else if ((dragToPlay_ && e->stateAfter() == Qt::LeftButton) || #endif (!dragToPlay_ && !srcPicked_ .isNull()) && !camera()->frame()->isManipulated()) { if (game_->phase() == RedPlacementPhase || game_->phase() == PiecePlacementPhase) { if (piecePicked_) { selectionMode_ = 2; select(e); updateGL(); } } else // phase == MovePhase { if (!srcPicked_.isNull()) { selectionMode_ = 4; select(e); // Since selection in mode 4 can work only for case with pieces, we // try to select a case if no case is selected yet. if (dstPicked_.isNull()) { selectionMode_ = 5; select(e); } updateGL(); } } } else QGLViewer::mouseMoveEvent(e); } void DvonnViewer::mouseReleaseEvent(QMouseEvent* e) { #if QT_VERSION >= 0x040000 if (e->button() == Qt::LeftButton) #else if (e->state() == Qt::LeftButton) #endif { if (dragToPlay_) { commitDstPicked(); } } else QGLViewer::mouseReleaseEvent(e); } void DvonnViewer::commitDstPicked() { if (game_->phase() == RedPlacementPhase || game_->phase() == PiecePlacementPhase) { if (piecePicked_ && !dstPicked_.isNull()) { Player p = game_->theOnePlaying(); emit requested(Game::Placement(game_->phase() == RedPlacementPhase?Red:colorOf(p), dstPicked_.stackCoord())); updateGL(); } } else // phase == MovePhase { if (!srcPicked_.isNull() && !dstPicked_.isNull()) { emit requested(Game::Move(srcPicked_.stackCoord(), dstPicked_.stackCoord())); } } piecePicked_ = false; dstPicked_ = Board::ConstStackHandle::null(); srcPicked_ = Board::ConstStackHandle::null(); setMouseTracking(false); updateGL(); } void DvonnViewer::keyPressEvent(QKeyEvent* e) { if (e->key() == Qt::Key_D && scoreT_ > 0.0f) { if (scoreTimer_->isActive()) scoreTimer_->stop(); else scoreTimer_->start(debugInterval); } else if (e->key() == Qt::Key_T) { toggleShowStatus(!showStatus_); } else QGLViewer::keyPressEvent(e); } QString DvonnViewer::helpString() const { QString text("

D v o n n

"); text += "See the Help/Rules of Dvonn menu for the rules of the game.

"; text += "Use the mouse left button to play. Middle and right buttons move camera."; text += "Use Ctrl+left to rotate the camera. See the mouse tab for complete mouse bindings."; return text; }