1 /***************************************************************************
2 * Copyright 2005-2007 Francesco Rossi <redsh@email.it> *
3 * Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net> *
4 * Copyright 2006-2008 Johannes Bergmeier <johannes.bergmeier@gmx.net> *
5 * Copyright 2012 Ian Wadham <iandw.au@gmail.com> *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
21 ***************************************************************************/
22
23 #include "roxdokuview.h"
24
25 #include "puzzle.h"
26 #include "ksudoku.h"
27 #include "skgraph.h"
28
29 #include <QCursor>
30 #include <QPixmap>
31 #include <KLocalizedString>
32
33 #include "settings.h"
34
35 #include "renderer.h"
36 #include "gameactions.h"
37
38 namespace ksudoku{
39
40 GLUquadricObj *quadratic; // Used For Our Quadric
41
42 //const float = 2.0*3.1415926535f; // PI Squared
43
44
45
46 GLfloat LightAmbient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
47 GLfloat LightDiffuse[] = { 0.8f, 1.0f, 1.0f, 1.0f };
48 GLfloat LightPosition[] = { 0.0f, 0.0f, -10.0f, 5.0f };
49
50 Matrix4fT Transform = {{ {1.0f}, {0.0f}, {0.0f}, {0.0f}, // NEW: Final Transform
51 {0.0f}, {1.0f}, {0.0f}, {0.0f},
52 {0.0f}, {0.0f}, {1.0f}, {0.0f},
53 {0.0f}, {0.0f}, {0.0f}, {1.0f} }};
54
55 Matrix3fT LastRot = {{ {1.0f}, {0.0f}, {0.0f}, // NEW: Last Rotation
56 {0.0f}, {1.0f}, {0.0f},
57 {0.0f}, {0.0f}, {1.0f} }};
58
59 Matrix3fT ThisRot = {{ {1.0f}, {0.0f}, {0.0f}, // NEW: This Rotation
60 {0.0f}, {1.0f}, {0.0f},
61 {0.0f}, {0.0f}, {1.0f} }};
62
63
RoxdokuView(const ksudoku::Game & game,GameActions * gameActions,QWidget * parent)64 RoxdokuView::RoxdokuView(const ksudoku::Game &game, GameActions * gameActions,
65 QWidget * parent)
66 : QGLWidget(parent)
67 {
68 m_game = game;
69 m_graph = m_game.puzzle()->graph();
70
71 m_order = m_graph->order();
72 m_base = m_graph->base();
73 m_size = m_graph->size();
74 m_width = m_graph->sizeX();
75 m_height = m_graph->sizeY();
76 m_depth = m_graph->sizeZ();
77
78 connect(m_game.interface(), &GameIFace::cellChange, this, &QGLWidget::updateGL);
79 connect(m_game.interface(), &GameIFace::fullChange, this, &QGLWidget::updateGL);
80 connect(gameActions, &GameActions::enterValue, this, &RoxdokuView::enterValue);
81
82 // IDW test. m_wheelmove = 0.0f;
83 m_wheelmove = -5.0f; // IDW test. Makes the viewport bigger, can see more.
84 m_dist = 5.3f;
85 m_selected_number = 1;
86
87 loadSettings();
88
89 m_isClicked = false;
90 m_isRClicked = false;
91 m_isDragging = false;
92
93 m_selection = -1;
94 m_lastSelection = -1;
95 m_highlights.fill(0, m_size);
96 m_timeDelay = false;
97 m_delayTimer = new QTimer(this);
98 connect(m_delayTimer, &QTimer::timeout, this, &RoxdokuView::delayOver);
99 }
100
~RoxdokuView()101 RoxdokuView::~RoxdokuView()
102 {
103 glDeleteTextures(10, m_texture[0]);
104 glDeleteTextures(25, m_texture[1]);
105 }
106
enterValue(int value)107 void RoxdokuView::enterValue(int value)
108 {
109 if (m_selection >= 0) {
110 m_game.setValue(m_selection, value);
111 updateGL();
112 }
113 }
114
status() const115 QString RoxdokuView::status() const
116 {
117 QString m;
118
119 // int secs = QTime(0,0).secsTo(m_game.time());
120 // if(secs % 36 < 12)
121 // m = i18n("Selected item %1, Time elapsed %2. DRAG to rotate. MOUSE WHEEL to zoom in/out.",
122 // m_symbols->value2Symbol(m_selected_number, m_game.order()),
123 // m_game.time().toString("hh:mm:ss"));
124 // else if(secs % 36 < 24)
125 // m = i18n("Selected item %1, Time elapsed %2. DOUBLE CLICK on a cube to insert selected number.",
126 // m_symbols->value2Symbol(m_selected_number, m_game.order()),
127 // m_game.time().toString("hh:mm:ss"));
128 // else
129 // m = i18n("Selected item %1, Time elapsed %2. Type in a cell (zero to delete) to place that number in it.",
130 // m_symbols->value2Symbol(m_selected_number, m_game.order()),
131 // m_game.time().toString("hh:mm:ss"));
132
133 return m;
134 }
135
136
initializeGL()137 void RoxdokuView::initializeGL()
138 {
139 glClearColor( 0.0, 0.0, 0.0, 0.5 );
140 glEnable(GL_TEXTURE_2D); // Enable Texture Mapping ( NEW )
141 //glShadeModel(GL_SMOOTH); // Enable Smooth Shading
142 //glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
143 //glClearDepth(1.0f); // Depth Buffer Setup
144 glEnable(GL_DEPTH_TEST); // Enables Depth Testing
145 //glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
146 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
147
148 setMouseTracking(true);
149
150 for(int o=0; o<2; o++)
151 for(int i=0; i<=9+o*16; i++)
152 {
153 int sz = 64;
154 QPixmap pic = Renderer::instance()->renderSpecial3D(SpecialCell, sz);
155 if(i != 0) {
156 pic = Renderer::instance()->renderSymbolOn(pic, i, 0, 9+o*16, SymbolPreset);
157 }
158 QImage pix = convertToGLFormat(pic.toImage());
159
160 glGenTextures(1, &m_texture[o][i]);
161 glBindTexture(GL_TEXTURE_2D, m_texture[o][i]);
162 glTexImage2D(GL_TEXTURE_2D, 0,4, sz,sz, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*) pix.bits());
163 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
164 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering
165 }
166 }
167
resizeGL(int w,int h)168 void RoxdokuView::resizeGL(int w, int h ) {
169 if (w == 0) w = 1;
170 if (h == 0) h = 1;
171 m_arcBall = new ArcBallT((GLfloat)w, (GLfloat)h);
172
173 glViewport(0, 0, (GLint)w, (GLint)h);
174 glMatrixMode(GL_PROJECTION); // Select the Projection Matrix
175 glLoadIdentity(); // Reset the Projection Matrix
176
177 gluPerspective(45.0f, (GLfloat)w / (GLfloat)h, 0.1f, 100.0f);
178
179 glMatrixMode(GL_MODELVIEW); // Select the Modelview Matrix
180 glLoadIdentity();
181 }
182
183
mouseDoubleClickEvent(QMouseEvent *)184 void RoxdokuView::mouseDoubleClickEvent ( QMouseEvent * /*e*/ )
185 {
186 if(m_selection == -1) return;
187 if(m_selected_number == -1) return;
188 if(m_game.given(m_selection)) return;
189 m_game.setValue(m_selection, m_selected_number);
190 // updateGL();
191 if(m_isDragging) releaseMouse();
192 }
193
Selection(int mouse_x,int mouse_y)194 void RoxdokuView::Selection(int mouse_x, int mouse_y)
195 {
196 if(m_isDragging)
197 return;
198
199 makeCurrent();
200
201 GLuint buffer[512];
202 GLint hits;
203
204 GLint viewport[4];
205
206 glGetIntegerv(GL_VIEWPORT, viewport);
207 glSelectBuffer(512, buffer);
208 (void) glRenderMode(GL_SELECT);
209
210 glInitNames();
211 glPushName(0);
212
213 glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix
214 glPushMatrix(); // Push The Projection Matrix
215 glLoadIdentity(); // Resets The Matrix
216
217 // This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is.
218 gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport);
219 gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);
220 glMatrixMode(GL_MODELVIEW);
221 paintGL();
222 glMatrixMode(GL_PROJECTION);
223 glPopMatrix();
224 glMatrixMode(GL_MODELVIEW);
225 hits=glRenderMode(GL_RENDER);
226
227 if (hits > 0){
228 int choose = buffer[3];
229 int depth = buffer[1];
230
231 for (int loop = 1; loop < hits; loop++){
232 // If This Object Is Closer To Us Than The One We Have Selected
233 if (buffer[loop*4+1] < GLuint(depth)){
234 choose = buffer[loop*4+3];
235 depth = buffer[loop*4+1];
236 }
237 }
238
239 if(choose <= m_size && choose > 0)
240 m_selection = choose-1;
241
242 // Stop the timer if the selection is on a cube.
243 if (m_timeDelay) {
244 m_delayTimer->stop();
245 m_timeDelay = false;
246 }
247 setFocus();
248 paintGL();
249 }
250 else if ((! m_timeDelay) && (m_selection != -1)) {
251 // Avoid flickering when the pointer passes between cubes.
252 m_delayTimer->start(300);
253 m_timeDelay = true;
254 }
255 }
256
delayOver()257 void RoxdokuView::delayOver()
258 {
259 // Remove the highlighting, etc. when the pointer rests between cubes.
260 m_delayTimer->stop();
261 m_timeDelay = false;
262 m_selection = -1;
263 paintGL();
264 }
265
mouseMoveEvent(QMouseEvent * e)266 void RoxdokuView::mouseMoveEvent ( QMouseEvent * e )
267 {
268 Point2fT f;
269 f.T[0] = e->x();
270 f.T[1] = e->y();
271
272 Selection(e->x(), e->y());
273
274 if (m_isRClicked){ // If Right Mouse Clicked, Reset All Rotations
275 Matrix3fSetIdentity(&LastRot); // Reset Rotation
276 Matrix3fSetIdentity(&ThisRot); // Reset Rotation
277 Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Reset Rotation
278 }
279
280 if (!m_isDragging){ // Not Dragging
281 if (m_isClicked){ // First Click
282 m_isDragging = true; // Prepare For Dragging
283 LastRot = ThisRot; // Set Last Static Rotation To Last Dynamic One
284 m_arcBall->click(&f); // Update Start Vector And Prepare For Dragging
285 grabMouse(/*QCursor(Qt::SizeAllCursor)*/);
286 }
287 updateGL();
288 }
289 else{
290 if (m_isClicked){ // Still Clicked, So Still Dragging
291 Quat4fT ThisQuat;
292
293 m_arcBall->drag(&f, &ThisQuat); // Update End Vector And Get Rotation As Quaternion
294 Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // Convert Quaternion Into Matrix3fT
295 Matrix3fMulMatrix3f(&ThisRot, &LastRot); // Accumulate Last Rotation Into This One
296 Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Set Our Final Transform's Rotation From This One
297 }
298 else{ // No Longer Dragging
299 m_isDragging = false;
300 releaseMouse ();
301 }
302 updateGL();
303 }
304 }
305
selectValue(int value)306 void RoxdokuView::selectValue(int value) {
307 m_selected_number = value;
308 }
309
loadSettings()310 void RoxdokuView::loadSettings() {
311 m_guidedMode = Settings::showErrors();
312 m_showHighlights = Settings::showHighlights3D();
313
314 float s = Settings::overallSize3D()/10.0f; // Normal size.
315 m_unhighlightedSize = s;
316 m_selectionSize = s * Settings::selectionSize3D()/10.0f;
317 m_highlightedSize = s * Settings::highlightedSize3D()/10.0f;
318 m_outerCellSize = s * Settings::outerCellSize3D()/10.0f;
319 m_darkenOuterCells = Settings::darkenOuterCells3D();
320 }
321
settingsChanged()322 void RoxdokuView::settingsChanged() {
323 loadSettings();
324 updateGL();
325 }
326
myDrawCube(bool highlight,int name,GLfloat x,GLfloat y,GLfloat z,bool outside)327 void RoxdokuView::myDrawCube(bool highlight, int name,
328 GLfloat x, GLfloat y, GLfloat z, bool outside)
329 {
330 glPushMatrix();
331 glLoadName(name+1);
332 glTranslatef(x,y,z);
333
334 glBindTexture(GL_TEXTURE_2D, m_texture[m_order >= 16][m_game.value(name)]);
335
336 float sz = 1.0f;
337 float s = 0.2f;
338 if(m_selection != -1 && m_selection != name && highlight) {
339 s = +0.2;
340 sz = m_highlightedSize;
341
342 switch(m_game.buttonState(name)) {
343 case ksudoku::GivenValue:
344 glColor3f(0.85f,1.0f,0.4f); // Green/Gold.
345 break;
346 case ksudoku::ObviouslyWrong:
347 case ksudoku::WrongValue:
348 if(m_guidedMode && m_game.puzzle()->hasSolution())
349 glColor3f(0.75f,0.25f,0.25f); // Red.
350 else
351 glColor3f(0.75f+s,0.75f+s,0.25f+s);
352 break;
353 case ksudoku::Marker:
354 case ksudoku::CorrectValue:
355 glColor3f(0.75f+s,0.75f+s,0.25f+s); // Gold.
356 break;
357 }
358 } else {
359 sz = m_unhighlightedSize;
360 s = 0.1f;
361 if (outside && (m_selection != -1)) {
362 // Shrink and darken cells outside the selection-volume.
363 sz = m_outerCellSize;
364 s = m_darkenOuterCells ? -0.24f : 0.0f;
365 }
366 switch(m_game.buttonState(name)) {
367 case ksudoku::GivenValue:
368 glColor3f(0.6f+s,0.9f+s,0.6f+s); // Green.
369 break;
370 case ksudoku::ObviouslyWrong:
371 case ksudoku::WrongValue:
372 if(m_guidedMode && m_game.puzzle()->hasSolution())
373 glColor3f(0.75f,0.25f,0.25f); // Red.
374 else
375 glColor3f(0.6f+s,1.0f+s,1.0f+s);// Blue.
376 break;
377 case ksudoku::Marker:
378 case ksudoku::CorrectValue:
379 glColor3f(0.6f+s,1.0f+s,1.0f+s); // Blue.
380 break;
381 }
382 }
383
384 if(m_selection == name) {
385 sz = m_selectionSize;
386 // IDW test. glColor3f(0.75f,0.25f,0.25f);
387 glColor3f(1.0f,0.8f,0.4f); // Orange.
388 }
389
390 glBegin(GL_QUADS);
391 /* front face */
392 glTexCoord2f(0.0f, 0.0f);
393 glVertex3f(-sz, -sz, sz);
394 glTexCoord2f(1.0f, 0.0f);
395 glVertex3f(sz, -sz, sz);
396 glTexCoord2f(1.0f, 1.0f);
397 glVertex3f(sz, sz, sz);
398 glTexCoord2f(0.0f, 1.0f);
399 glVertex3f(-sz, sz, sz);
400 /* back face */
401 glTexCoord2f(1.0f, 0.0f);
402 glVertex3f(-sz, -sz, -sz);
403 glTexCoord2f(1.0f, 1.0f);
404 glVertex3f(-sz, sz, -sz);
405 glTexCoord2f(0.0f, 1.0f);
406 glVertex3f(sz, sz, -sz);
407 glTexCoord2f(0.0f, 0.0f);
408 glVertex3f(sz, -sz, -sz);
409 /* right face */
410 glTexCoord2f(1.0f, 0.0f);
411 glVertex3f(sz, -sz, -sz);
412 glTexCoord2f(1.0f, 1.0f);
413 glVertex3f(sz, sz, -sz);
414 glTexCoord2f(0.0f, 1.0f);
415 glVertex3f(sz, sz, sz);
416 glTexCoord2f(0.0f, 0.0f);
417 glVertex3f(sz, -sz, sz);
418 /* left face */
419 glTexCoord2f(1.0f, 0.0f);
420 glVertex3f(-sz, -sz, sz);
421 glTexCoord2f(1.0f, 1.0f);
422 glVertex3f(-sz, sz, sz);
423 glTexCoord2f(0.0f, 1.0f);
424 glVertex3f(-sz, sz, -sz);
425 glTexCoord2f(0.0f, 0.0f);
426 glVertex3f(-sz, -sz, -sz);
427 /* top face */
428 glTexCoord2f(1.0f, 0.0f);
429 glVertex3f(sz, sz, sz);
430 glTexCoord2f(1.0f, 1.0f);
431 glVertex3f(sz, sz, -sz);
432 glTexCoord2f(0.0f, 1.0f);
433 glVertex3f(-sz, sz, -sz);
434 glTexCoord2f(0.0f, 0.0f);
435 glVertex3f(-sz, sz, sz);
436 /* bottom face */
437 glTexCoord2f(1.0f, 0.0f);
438 glVertex3f(sz, -sz, -sz);
439 glTexCoord2f(1.0f, 1.0f);
440 glVertex3f(sz, -sz, sz);
441 glTexCoord2f(0.0f, 1.0f);
442 glVertex3f(-sz, -sz, sz);
443 glTexCoord2f(0.0f, 0.0f);
444 glVertex3f(-sz, -sz, -sz);
445 glEnd();
446 glPopMatrix();
447 }
448
paintGL()449 void RoxdokuView::paintGL()
450 {
451 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
452 glLoadIdentity();
453
454 glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);
455 glTranslatef(0.0f, 0.0f, -m_dist*(m_width+3)+m_wheelmove);
456
457 glMultMatrixf(Transform.M);
458
459 enum {Outside, Inside, Highlight};
460 int selX = -1, selY = -1, selZ = -1;
461
462 // If a cell is newly selected work out the highlights and lowlights.
463 if ((m_selection != -1) && (m_selection != m_lastSelection)) {
464 m_lastSelection = m_selection;
465 selX = m_graph->cellPosX (m_selection);
466 selY = m_graph->cellPosY (m_selection);
467 selZ = m_graph->cellPosZ (m_selection);
468
469 // Note: m_highlights persists through many frame-paints per second.
470 m_highlights.fill(Outside, m_size);
471
472 // Mark the cells to be highlighted when highlighting is on.
473 QList<int> groupsToHighlight = m_graph->cliqueList(m_selection);
474 for(int g = 0; g < groupsToHighlight.count(); g++) {
475 QVector<int> cellList =
476 m_graph->clique(groupsToHighlight.at(g));
477 for (int n = 0; n < m_order; n++) {
478 m_highlights[cellList.at(n)] = Highlight;
479 }
480 }
481
482 // Mark non-highlighted cells that are inside cubes containing
483 // the selected cell. In custom Roxdoku puzzles with > 1 cube,
484 // cells outside are shrunk and darkened.
485 for (int n = 0; n < m_graph->structureCount(); n++) {
486 int cubePos = m_graph->structurePosition(n);
487 int cubeX = m_graph->cellPosX(cubePos);
488 int cubeY = m_graph->cellPosY(cubePos);
489 int cubeZ = m_graph->cellPosZ(cubePos);
490 if (m_graph->structureType(n) != SKGraph::RoxdokuGroups) {
491 continue;
492 }
493 if ((selX >= cubeX) && (selX < (cubeX + m_base)) &&
494 (selY >= cubeY) && (selY < (cubeY + m_base)) &&
495 (selZ >= cubeZ) && (selZ < (cubeZ + m_base))) {
496 for (int x = cubeX; x < cubeX + m_base; x++) {
497 for (int y = cubeY; y < cubeY + m_base; y++) {
498 for (int z = cubeZ; z < cubeZ + m_base; z++) {
499 int pos = m_graph->cellIndex(x, y, z);
500 if (m_highlights.at(pos) == Outside) {
501 m_highlights[pos] = Inside;
502 }
503 }
504 }
505 }
506 }
507 }
508 }
509
510 int c = 0;
511
512 for(int xx = 0; xx < m_width; ++xx) {
513 for(int yy = 0; yy < m_height; ++yy) {
514 for(int zz = 0; zz < m_depth; ++zz) {
515 if(m_game.value(c) == UNUSABLE) {
516 c++;
517 continue; // Do not paint unusable cells.
518 }
519 glPushMatrix();
520
521 // Centre the puzzle in the viewport.
522 glTranslatef(-(m_dist * m_width - m_dist) / 2,
523 -(m_dist * m_height - m_dist) / 2,
524 -(m_dist * m_depth - m_dist) / 2);
525
526 // Highlight cells in the three planes through
527 // the selected cell. Unhighlight cells outside
528 // the cubical volume of the selection.
529 bool highlight = m_showHighlights &&
530 (m_highlights.at(c) == Highlight);
531 bool outside = (m_highlights.at(c) == Outside);
532
533 myDrawCube(highlight, c++,
534 (GLfloat)(m_dist * xx),
535 (GLfloat)(m_dist * yy),
536 (GLfloat)(m_dist * zz), outside);
537
538 glPopMatrix();
539 }
540 }
541 }
542 swapBuffers();
543 }
544
545 }
546
547
548