1 /****************************************************************************
2 
3  Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.7.2.
6 
7  http://www.libqglviewer.com - contact@libqglviewer.com
8 
9  This file may be used under the terms of the GNU General Public License
10  versions 2.0 or 3.0 as published by the Free Software Foundation and
11  appearing in the LICENSE file included in the packaging of this file.
12  In addition, as a special exception, Gilles Debunne gives you certain
13  additional rights, described in the file GPL_EXCEPTION in this package.
14 
15  libQGLViewer uses dual licensing. Commercial/proprietary software must
16  purchase a libQGLViewer Commercial License.
17 
18  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 
21 *****************************************************************************/
22 
23 #include "dvonnviewer.h"
24 #include "drawer.h"
25 #include "game.h"
26 #include <fstream>
27 #include <math.h>
28 #include <qmessagebox.h>
29 
30 #include <QGLViewer/manipulatedCameraFrame.h>
31 
32 #include <QMouseEvent>
33 
34 using namespace std;
35 using namespace qglviewer;
36 using namespace dvonn;
37 
38 namespace {
39 int debugInterval;
40 const unsigned int nbAnimationSteps = 30;
41 class BoardConstraint : public Constraint {
42 public:
constrainRotation(Quaternion & q,Frame * const fr)43   virtual void constrainRotation(Quaternion &q, Frame *const fr) {
44     const Vec up = fr->transformOf(Vec(0.0, 0.0, 1.0));
45     Vec axis = q.axis();
46     float angle = 2.0 * acos(q[3]);
47     if (fabs(axis * up) > fabs(axis.x))
48       axis.projectOnAxis(up);
49     else {
50       angle = (axis.x > 0.0) ? angle : -angle;
51       axis.setValue(fabs(axis.x), 0.0, 0.0);
52       const float currentAngle =
53           asin(fr->inverseTransformOf(Vec(0.0, 0.0, -1.0)).z);
54       if (currentAngle + angle > -0.2)
55         angle = -0.2 - currentAngle; // Not too low
56       if (currentAngle + angle < -M_PI / 2.0)
57         angle = -M_PI / 2.0 - currentAngle; // Do not pass on the other side
58     }
59     q = Quaternion(axis, angle);
60   }
61 };
62 } // namespace
63 //************************************************************
64 // Implementation of DvonnViewer
65 //************************************************************
66 #if QT_VERSION < 0x040000
DvonnViewer(QWidget * parent,const char * name)67 DvonnViewer::DvonnViewer(QWidget *parent, const char *name)
68     : QGLViewer(parent, name),
69 #else
70 DvonnViewer::DvonnViewer(QWidget *parent)
71     : QGLViewer(parent),
72 #endif
73       game_(NULL), selectionMode_(-1), piecePicked_(false),
74       dstPicked_(Board::ConstStackHandle::null()),
75       srcPicked_(Board::ConstStackHandle::null()), showPossDest_(true),
76       showStatus_(false), showLabels_(false), useLight_(true),
77       dragToPlay_(false), fadeGhosts_(NULL), animateT_(-1.0f),
78       showAnimation_(true), scoreT_(-1.0f) {
79   drawer_ = new Drawer;
80   fadeTimer_ = new QTimer();
81   connect(fadeTimer_, SIGNAL(timeout()), this, SLOT(advanceFadeOut()));
82   animateTimer_ = new QTimer();
83   connect(animateTimer_, SIGNAL(timeout()), this, SLOT(advanceAnimateMove()));
84   scoreTimer_ = new QTimer();
85   connect(scoreTimer_, SIGNAL(timeout()), this, SLOT(advanceAnimateScore()));
86 }
~DvonnViewer()87 DvonnViewer::~DvonnViewer() {
88   delete drawer_;
89   delete fadeTimer_;
90   delete animateTimer_;
91   delete scoreTimer_;
92 }
advanceFadeOut()93 void DvonnViewer::advanceFadeOut() {
94   fadeAlpha_ -= 0.05f;
95   if (fadeAlpha_ < 0.0f) {
96     fadeTimer_->stop();
97     fadeAlpha_ = 0.0f;
98     fadeGhosts_ = NULL;
99   }
100   update();
101 }
fadeOut(const Board::Ghosts * g)102 void DvonnViewer::fadeOut(const Board::Ghosts *g) {
103   if ((fadeGhosts_ = g) != NULL && !g->empty()) {
104     fadeAlpha_ = 1.0f;
105     fadeTimer_->start(30);
106   }
107 }
advanceAnimateMove()108 void DvonnViewer::advanceAnimateMove() {
109   animateT_ += 1.0f / nbAnimationSteps;
110   if (animateT_ >= 1.0f) {
111     animateTimer_->stop();
112     animateT_ = -1.0f;
113     Q_EMIT requested(animateMove_);
114   }
115   update();
116 }
animateMove(Game::Move m)117 void DvonnViewer::animateMove(Game::Move m) {
118   if (animateT_ < 0.0f) {
119     if (showAnimation_) {
120       animateMove_ = m;
121       animateT_ = 0.0f;
122       static const float v = 1.0f / 250;
123       const float d = drawer_->estimateDrawMoveLength(game_->board(), m);
124       const float T = d / v;
125       animateTimer_->start(static_cast<int>(T / nbAnimationSteps));
126     } else {
127       Q_EMIT requested(m);
128     }
129   }
130 }
advanceAnimateScore()131 void DvonnViewer::advanceAnimateScore() {
132   scoreT_ += 1.0f / nbAnimationSteps;
133   if (scoreT_ >= 1.0f) {
134     scoreTimer_->stop();
135     scoreT_ = -1.0f;
136     Q_EMIT requested(scoreMove_);
137   }
138   update();
139 }
animateScore()140 void DvonnViewer::animateScore() {
141   if (scoreT_ < 0.0f) {
142     // Stack will be moved to to positions at center of the board
143     static const Board::Coord centers[2] = {
144         Board::Coord(Board::nbSpacesMaxOnRow() / 2, Board::nbRows() / 2 + 1),
145         Board::Coord(Board::nbSpacesMaxOnRow() / 2 + 1,
146                      Board::nbRows() / 2 + 1)};
147     // Search for a stack to move
148     for (Board::ConstStackIterator iter = game_->board().stacks_begin(),
149                                    istop = game_->board().stacks_end();
150          iter != istop; ++iter) {
151       if (iter.stackCoord() != centers[0] && iter.stackCoord() != centers[1] &&
152           iter->hasPieces()) {
153         Color c = iter->onTop()->color();
154         if (c != Red) {
155           scoreMove_ = Game::Move(iter.stackCoord(), centers[c - 1]);
156           scoreT_ = 0.0f;
157           static const float v = 1.0f / 250;
158           const float d =
159               drawer_->estimateDrawMoveLength(game_->board(), scoreMove_);
160           const float T = d / v;
161           scoreTimer_->start(debugInterval =
162                                  static_cast<int>(T / nbAnimationSteps));
163           return;
164         }
165       }
166     }
167   }
168 }
stopAllAnimations()169 void DvonnViewer::stopAllAnimations() {
170   fadeTimer_->stop();
171   fadeAlpha_ = 0.0f;
172   fadeGhosts_ = NULL;
173 
174   animateTimer_->stop();
175   animateT_ = -1.0f;
176 
177   scoreTimer_->stop();
178   scoreT_ = -1.0f;
179 
180   update();
181 }
setGame(Game * g)182 void DvonnViewer::setGame(Game *g) { game_ = g; }
toggleTexture(bool b)183 void DvonnViewer::toggleTexture(bool b) {
184   drawer_->toggleTexture(b);
185   update();
186 }
toggleLight(bool b)187 void DvonnViewer::toggleLight(bool b) {
188   useLight_ = b;
189   update();
190 }
toggleShowPossible(bool b)191 void DvonnViewer::toggleShowPossible(bool b) {
192   showPossDest_ = b;
193   if (showPossDest_ && game_->phase() == MovePhase && !srcPicked_.isNull()) {
194     possDests_ = game_->possibleDestinations(srcPicked_);
195   }
196   update();
197 }
toggleShowStatus(bool b)198 void DvonnViewer::toggleShowStatus(bool b) {
199   showStatus_ = b;
200   update();
201 }
toggleShowLabels(bool b)202 void DvonnViewer::toggleShowLabels(bool b) {
203   showLabels_ = b;
204   update();
205 }
toggleShowAnimation(bool b)206 void DvonnViewer::toggleShowAnimation(bool b) {
207   showAnimation_ = b;
208   update();
209 }
toggleDragToPlay(bool b)210 void DvonnViewer::toggleDragToPlay(bool b) {
211   dragToPlay_ = b;
212   update();
213 }
214 // I n i t i a l i z a t i o n   f u n c t i o n s //
init()215 void DvonnViewer::init() {
216   initOpenGL();
217   initSpotLight();
218   initViewer();
219 
220   drawer_->init();
221 }
initOpenGL()222 void DvonnViewer::initOpenGL() {
223   glCullFace(GL_BACK);
224   glEnable(GL_BLEND);
225   glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
226   glEnable(GL_TEXTURE_2D);
227   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
228 }
initSpotLight()229 void DvonnViewer::initSpotLight() {
230   const GLfloat light_ambient[4] = {1.0, 1.0, 1.0, 1.0};
231   const GLfloat light_specular[4] = {1.0, 1.0, 1.0, 1.0};
232   const GLfloat light_diffuse[4] = {1.0, 1.0, 1.0, 1.0};
233 
234   glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);
235   glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 60.0);
236   glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 0.1f);
237   glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.3f);
238   glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.3f);
239   glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);
240   glLightfv(GL_LIGHT1, GL_SPECULAR, light_specular);
241   glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
242 
243   const Vec pos = drawer_->boardCenter() +
244                   drawer_->boardRadius() * drawer_->boardUpVector();
245   const float posv[4] = {pos.x, pos.y, pos.z, 1.0f};
246   const Vec dir = -drawer_->boardUpVector();
247   const float dirv[4] = {dir.x, dir.y, dir.z, 1.0f};
248   glLightfv(GL_LIGHT1, GL_POSITION, posv);
249   glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, dirv);
250 }
initViewer()251 void DvonnViewer::initViewer() {
252   setSceneCenter(drawer_->boardCenter());
253   setSceneRadius(drawer_->boardRadius());
254   camera()->setUpVector(drawer_->boardUpVector());
255   camera()->setPosition(drawer_->defaultEyePosition());
256   camera()->lookAt(sceneCenter());
257   // Limit camera rotation motion
258   camera()->frame()->setConstraint(new BoardConstraint());
259 
260   clearMouseBindings();
261   // Defines new bindings
262   setMouseBindingDescription(Qt::NoModifier, Qt::LeftButton, "Moves stack");
263   setMouseBinding(Qt::ControlModifier, Qt::LeftButton, CAMERA, ROTATE);
264   setMouseBinding(Qt::NoModifier, Qt::RightButton, CAMERA, TRANSLATE);
265   setMouseBinding(Qt::NoModifier, Qt::MidButton, ZOOM_TO_FIT, true);
266   setMouseBinding(Qt::NoModifier, Qt::RightButton, ZOOM_TO_FIT, true);
267   setMouseBinding(Qt::ControlModifier, Qt::LeftButton, RAP_FROM_PIXEL, true);
268   setMouseBinding(Qt::ControlModifier, Qt::RightButton, RAP_IS_CENTER, true);
269 
270   setWheelBinding(Qt::NoModifier, CAMERA, ZOOM);
271 
272 #if QT_VERSION >= 0x040000
273   setWheelBinding(Qt::ControlModifier, FRAME, NO_MOUSE_ACTION);
274 #else
275   setWheelBinding(Qt::ControlButton, FRAME, NO_MOUSE_ACTION);
276 #endif
277 }
draw()278 void DvonnViewer::draw() {
279   (useLight_ ? glEnable : glDisable)(GL_LIGHTING);
280   glEnable(GL_LIGHT1);
281 
282   drawAllSpaces();
283   drawer_->drawComplement(showLabels_);
284   drawAllPieces();
285   Player p = game_->theOnePlaying();
286   Color c = game_->phase() == RedPlacementPhase ? Red : colorOf(p);
287   drawer_->drawWhitePiecePools(game_->board(),
288                                p == WhitePlayer && piecePicked_);
289   drawer_->drawBlackPiecePools(game_->board(),
290                                p == BlackPlayer && piecePicked_);
291   if (piecePicked_ && !dstPicked_.isNull()) {
292     drawer_->drawTransparentPiece(c, dstPicked_);
293   }
294   if (!piecePicked_ && !srcPicked_.isNull()) {
295     drawer_->drawTransparentPieces(srcPicked_->begin(), srcPicked_->end(),
296                                    srcPicked_.stackCoord(), 0.0f, 0.4f);
297     if (showPossDest_) {
298       glColor3f(1.0f, 1.0f, 0.0f);
299       for (deque<Board::ConstStackHandle>::const_iterator iter =
300                possDests_.begin();
301            iter != possDests_.end(); ++iter) {
302         drawer_->highlightPieces(*iter);
303       }
304     }
305     if (!dstPicked_.isNull()) {
306       drawer_->drawTransparentPieces(srcPicked_->begin(), srcPicked_->end(),
307                                      dstPicked_.stackCoord(),
308                                      dstPicked_->height(), 0.9f);
309     }
310   }
311   // Ghosts
312   if (fadeGhosts_) {
313     for (Board::Ghosts::const_iterator iter = fadeGhosts_->begin();
314          iter != fadeGhosts_->end(); ++iter) {
315       drawer_->drawTransparentPieces(iter->stack.begin(), iter->stack.end(),
316                                      iter->coord, 0.0f,
317                                      fadeAlpha_ * fadeAlpha_);
318     }
319   }
320   // Animated move
321   if (animateT_ >= 0.0f) {
322     drawer_->drawMove(game_->board(), animateMove_, animateT_);
323   }
324   if (scoreT_ >= 0.0f) {
325     drawer_->drawMove(game_->board(), scoreMove_, scoreT_);
326   }
327 }
drawAllPieces(bool pick)328 void DvonnViewer::drawAllPieces(bool pick) {
329   unsigned int name = 0;
330   for (Board::ConstStackIterator iter = game_->board().stacks_begin(),
331                                  istop = game_->board().stacks_end();
332        iter != istop; ++iter) {
333     if (pick)
334       glPushName(name++);
335     if (srcPicked_ != iter &&
336         (animateT_ < 0.0f || iter.stackCoord() != animateMove_.src) &&
337         (scoreT_ < 0.0f || iter.stackCoord() != scoreMove_.src)) {
338       drawer_->drawPieces(iter);
339     }
340     if (pick)
341       glPopName();
342   }
343   if (showStatus_ && !pick) {
344     glPushAttrib(GL_ALL_ATTRIB_BITS);
345     glDisable(GL_LIGHTING);
346     glColor3f(0.0, 1.0, 0.0f);
347     for (Board::ConstStackIterator iter = game_->board().stacks_begin(),
348                                    istop = game_->board().stacks_end();
349          iter != istop; ++iter) {
350       drawer_->drawStatus(iter, this);
351     }
352   }
353   glPopAttrib();
354 }
drawAllSpaces(bool pick)355 void DvonnViewer::drawAllSpaces(bool pick) {
356   unsigned int name = 0;
357   for (Board::ConstStackIterator iter = game_->board().stacks_begin(),
358                                  istop = game_->board().stacks_end();
359        iter != istop; ++iter) {
360     if (pick)
361       glPushName(name++);
362     drawer_->draw(iter);
363     if (pick)
364       glPopName();
365   }
366 }
drawWithNames()367 void DvonnViewer::drawWithNames() {
368   if (game_->isOver())
369     return;
370   switch (selectionMode_) {
371   case 1:
372     glPushName(0);
373     if (game_->theOnePlaying() == WhitePlayer) {
374       drawer_->drawWhitePiecePools(game_->board(), false);
375     } else {
376       drawer_->drawBlackPiecePools(game_->board(), false);
377     }
378     glPopName();
379     break;
380   case 2:
381   case 5:
382     drawAllSpaces(true);
383     break;
384   case 3:
385   case 4:
386     drawAllPieces(true);
387     break;
388   default:
389     cout << "No selection mode active!" << endl;
390   }
391 }
postSelection(const QPoint &)392 void DvonnViewer::postSelection(const QPoint &) {
393   switch (selectionMode_) {
394   case 1:
395     piecePicked_ = (selectedName() != -1);
396     break;
397   case 2:
398     if (selectedName() != -1) {
399       Board::ConstStackIterator iter = game_->board().stacks_begin();
400       for (int i = 0; i < selectedName(); ++i)
401         ++iter;
402       dstPicked_ = iter;
403       if (dstPicked_->hasPieces())
404         dstPicked_ = Board::ConstStackHandle::null();
405     } else {
406       dstPicked_ = Board::ConstStackHandle::null();
407     }
408     break;
409   case 3:
410     if (selectedName() != -1) {
411       Board::ConstStackIterator iter = game_->board().stacks_begin();
412       for (int i = 0; i < selectedName(); ++i)
413         ++iter;
414       srcPicked_ = iter;
415       if (!srcPicked_.isNull() &&
416           srcPicked_->onTop()->color() != colorOf(game_->theOnePlaying())) {
417         srcPicked_ = Board::ConstStackHandle::null();
418       }
419     } else {
420       srcPicked_ = Board::ConstStackHandle::null();
421     }
422     break;
423   case 4:
424   case 5:
425     if (selectedName() != -1) {
426       Board::ConstStackIterator iter = game_->board().stacks_begin();
427       for (int i = 0; i < selectedName(); ++i)
428         ++iter;
429       dstPicked_ = iter;
430       if (dstPicked_ == srcPicked_) {
431         dstPicked_ = Board::ConstStackHandle::null();
432       }
433       if (!dstPicked_.isNull() &&
434           !game_->isLegalMove(
435               Game::Move(srcPicked_.stackCoord(), dstPicked_.stackCoord()))) {
436         dstPicked_ = Board::ConstStackHandle::null();
437       }
438     } else {
439       dstPicked_ = Board::ConstStackHandle::null();
440     }
441     break;
442   };
443   selectionMode_ = -1;
444 }
mousePressEvent(QMouseEvent * e)445 void DvonnViewer::mousePressEvent(QMouseEvent *e) {
446 #if QT_VERSION >= 0x040000
447   if (e->button() == Qt::LeftButton)
448 #else
449   if (e->stateAfter() == Qt::LeftButton)
450 #endif
451   {
452     if (game_->phase() == RedPlacementPhase ||
453         game_->phase() == PiecePlacementPhase) {
454       if (dragToPlay_) {
455         selectionMode_ = 1;
456         select(e);
457         if (!dstPicked_.isNull()) {
458           piecePicked_ = true;
459         }
460       } else {
461         piecePicked_ = true;
462         ;
463         Board::ConstStackHandle firstClickDstPicked = dstPicked_;
464         selectionMode_ = 2;
465         select(e);
466         if (dstPicked_ == firstClickDstPicked) {
467           commitDstPicked();
468         } else if (dstPicked_.isNull()) {
469           piecePicked_ = false;
470         }
471       }
472       update();
473     } else // phase == MovePhase
474     {
475       if (dragToPlay_ || (!dragToPlay_ && srcPicked_.isNull())) {
476         selectionMode_ = 3;
477         select(e);
478         // Check that the picked src is free
479         if (!srcPicked_.isNull() && !game_->board().isFree(srcPicked_)) {
480           srcPicked_ = Board::ConstStackHandle::null();
481         }
482         // If asked, search for possible destinations
483         if (showPossDest_ && !srcPicked_.isNull()) {
484           possDests_ = game_->possibleDestinations(srcPicked_);
485         }
486         setMouseTracking(true);
487       } else {
488         selectionMode_ = 4;
489         select(e);
490         // Since selection in mode 4 can work only for case with pieces, we
491         // try to select a case if no case is selected yet.
492         if (dstPicked_.isNull()) {
493           selectionMode_ = 5;
494           select(e);
495         }
496         commitDstPicked();
497       }
498       update();
499     }
500   } else
501     QGLViewer::mousePressEvent(e);
502 }
mouseMoveEvent(QMouseEvent * e)503 void DvonnViewer::mouseMoveEvent(QMouseEvent *e) {
504 #if QT_VERSION >= 0x040000
505   if ((dragToPlay_ && e->button() == Qt::LeftButton) ||
506 #else
507   if ((dragToPlay_ && e->stateAfter() == Qt::LeftButton) ||
508 #endif
509       ((!dragToPlay_) && (!srcPicked_.isNull()) &&
510        (!camera()->frame()->isManipulated()))) {
511     if (game_->phase() == RedPlacementPhase ||
512         game_->phase() == PiecePlacementPhase) {
513       if (piecePicked_) {
514         selectionMode_ = 2;
515         select(e);
516         update();
517       }
518     } else // phase == MovePhase
519     {
520       if (!srcPicked_.isNull()) {
521         selectionMode_ = 4;
522         select(e);
523         // Since selection in mode 4 can work only for case with pieces, we
524         // try to select a case if no case is selected yet.
525         if (dstPicked_.isNull()) {
526           selectionMode_ = 5;
527           select(e);
528         }
529         update();
530       }
531     }
532   } else
533     QGLViewer::mouseMoveEvent(e);
534 }
mouseReleaseEvent(QMouseEvent * e)535 void DvonnViewer::mouseReleaseEvent(QMouseEvent *e) {
536 #if QT_VERSION >= 0x040000
537   if (e->button() == Qt::LeftButton)
538 #else
539   if (e->state() == Qt::LeftButton)
540 #endif
541   {
542     if (dragToPlay_) {
543       commitDstPicked();
544     }
545   } else
546     QGLViewer::mouseReleaseEvent(e);
547 }
commitDstPicked()548 void DvonnViewer::commitDstPicked() {
549   if (game_->phase() == RedPlacementPhase ||
550       game_->phase() == PiecePlacementPhase) {
551     if (piecePicked_ && !dstPicked_.isNull()) {
552       Player p = game_->theOnePlaying();
553       Q_EMIT requested(Game::Placement(
554           game_->phase() == RedPlacementPhase ? Red : colorOf(p),
555           dstPicked_.stackCoord()));
556       update();
557     }
558   } else // phase == MovePhase
559   {
560     if (!srcPicked_.isNull() && !dstPicked_.isNull()) {
561       Q_EMIT requested(
562           Game::Move(srcPicked_.stackCoord(), dstPicked_.stackCoord()));
563     }
564   }
565   piecePicked_ = false;
566   dstPicked_ = Board::ConstStackHandle::null();
567   srcPicked_ = Board::ConstStackHandle::null();
568   setMouseTracking(false);
569 
570   update();
571 }
keyPressEvent(QKeyEvent * e)572 void DvonnViewer::keyPressEvent(QKeyEvent *e) {
573   if (e->key() == Qt::Key_D && scoreT_ > 0.0f) {
574     if (scoreTimer_->isActive())
575       scoreTimer_->stop();
576     else
577       scoreTimer_->start(debugInterval);
578   } else if (e->key() == Qt::Key_T) {
579     toggleShowStatus(!showStatus_);
580   } else
581     QGLViewer::keyPressEvent(e);
582 }
helpString() const583 QString DvonnViewer::helpString() const {
584   QString text("<h2>D v o n n</h2>");
585   text += "See the <i>Help/Rules of Dvonn</i> menu for the rules of the "
586           "game.<br><br>";
587   text += "Use the mouse left button to play. Middle and right buttons move "
588           "camera.";
589   text += "Use <b>Ctrl+left</b> to rotate the camera. See the mouse tab for "
590           "complete mouse bindings.";
591   return text;
592 }
593