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