/**************************************************************************** Copyright (C) 2002-2008 Gilles Debunne. All rights reserved. This file is part of the QGLViewer library version 2.3.9. 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 "dvonnviewer.h" #include "game.h" #include "drawer.h" #include <fstream> #include <math.h> #include <qmessagebox.h> #if QT_VERSION >= 0x040000 # include <QMouseEvent> #endif 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; Q_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<int>(T/nbAnimationSteps)); } else { Q_EMIT requested(m); } } } void DvonnViewer::advanceAnimateScore() { scoreT_ += 1.0f/nbAnimationSteps; if (scoreT_ >= 1.0f) { scoreTimer_->stop(); scoreT_ = -1.0f; Q_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<int>(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.1f); glLightf( GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.3f); glLightf( GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.3f); 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<Board::ConstStackHandle>::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!"<<endl; } } void DvonnViewer::postSelection(const QPoint&) { switch (selectionMode_) { case 1: piecePicked_ = (selectedName() != -1); break; case 2: if (selectedName() != -1) { Board::ConstStackIterator iter = game_->board().stacks_begin(); for (int i=0;i<selectedName();++i) ++iter; dstPicked_ = iter; if (dstPicked_->hasPieces()) 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;i<selectedName();++i) ++iter; srcPicked_ = iter; if (!srcPicked_.isNull() && srcPicked_->onTop()->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;i<selectedName();++i) ++iter; dstPicked_ = iter; if (dstPicked_ == srcPicked_) { dstPicked_ = Board::ConstStackHandle::null(); } if (!dstPicked_.isNull() && !game_->isLegalMove(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(); Q_EMIT requested(Game::Placement(game_->phase() == RedPlacementPhase?Red:colorOf(p), dstPicked_.stackCoord())); updateGL(); } } else // phase == MovePhase { if (!srcPicked_.isNull() && !dstPicked_.isNull()) { Q_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("<h2>D v o n n</h2>"); text += "See the <i>Help/Rules of Dvonn</i> menu for the rules of the game.<br><br>"; text += "Use the mouse left button to play. Middle and right buttons move camera."; text += "Use <b>Ctrl+left</b> to rotate the camera. See the mouse tab for complete mouse bindings."; return text; }