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