File indexing completed on 2024-05-19 04:07:21
0001 /*************************************************************************** 0002 * Copyright 2005-2007 Francesco Rossi <redsh@email.it> * 0003 * Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net> * 0004 * Copyright 2006-2008 Johannes Bergmeier <johannes.bergmeier@gmx.net> * 0005 * Copyright 2012 Ian Wadham <iandw.au@gmail.com> * 0006 * * 0007 * This program is free software; you can redistribute it and/or modify * 0008 * it under the terms of the GNU General Public License as published by * 0009 * the Free Software Foundation; either version 2 of the License, or * 0010 * (at your option) any later version. * 0011 * * 0012 * This program is distributed in the hope that it will be useful, * 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0015 * GNU General Public License for more details. * 0016 * * 0017 * You should have received a copy of the GNU General Public License * 0018 * along with this program; if not, write to the * 0019 * Free Software Foundation, Inc., * 0020 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 0021 ***************************************************************************/ 0022 0023 #include "roxdokuview.h" 0024 0025 #include "puzzle.h" 0026 #include "ksudoku.h" 0027 #include "skgraph.h" 0028 0029 #include <QCursor> 0030 #include <QPixmap> 0031 #include <KLocalizedString> 0032 0033 #include "settings.h" 0034 0035 #include "renderer.h" 0036 #include "gameactions.h" 0037 0038 namespace ksudoku{ 0039 0040 GLUquadricObj *quadratic; // Used For Our Quadric 0041 0042 //const float = 2.0*3.1415926535f; // PI Squared 0043 0044 0045 0046 GLfloat LightAmbient[] = { 0.5f, 0.5f, 0.5f, 1.0f }; 0047 GLfloat LightDiffuse[] = { 0.8f, 1.0f, 1.0f, 1.0f }; 0048 GLfloat LightPosition[] = { 0.0f, 0.0f, -10.0f, 5.0f }; 0049 0050 Matrix4fT Transform = {{ {1.0f}, {0.0f}, {0.0f}, {0.0f}, // NEW: Final Transform 0051 {0.0f}, {1.0f}, {0.0f}, {0.0f}, 0052 {0.0f}, {0.0f}, {1.0f}, {0.0f}, 0053 {0.0f}, {0.0f}, {0.0f}, {1.0f} }}; 0054 0055 Matrix3fT LastRot = {{ {1.0f}, {0.0f}, {0.0f}, // NEW: Last Rotation 0056 {0.0f}, {1.0f}, {0.0f}, 0057 {0.0f}, {0.0f}, {1.0f} }}; 0058 0059 Matrix3fT ThisRot = {{ {1.0f}, {0.0f}, {0.0f}, // NEW: This Rotation 0060 {0.0f}, {1.0f}, {0.0f}, 0061 {0.0f}, {0.0f}, {1.0f} }}; 0062 0063 0064 RoxdokuView::RoxdokuView(const ksudoku::Game &game, GameActions * gameActions, 0065 QWidget * parent) 0066 : QOpenGLWidget(parent) 0067 { 0068 m_game = game; 0069 m_graph = m_game.puzzle()->graph(); 0070 0071 m_order = m_graph->order(); 0072 m_base = m_graph->base(); 0073 m_size = m_graph->size(); 0074 m_width = m_graph->sizeX(); 0075 m_height = m_graph->sizeY(); 0076 m_depth = m_graph->sizeZ(); 0077 0078 connect(m_game.interface(), &GameIFace::cellChange, this, qOverload<>(&QWidget::update)); 0079 connect(m_game.interface(), &GameIFace::fullChange, this, qOverload<>(&QWidget::update)); 0080 connect(gameActions, &GameActions::enterValue, this, &RoxdokuView::enterValue); 0081 0082 // IDW test. m_wheelmove = 0.0f; 0083 m_wheelmove = -5.0f; // IDW test. Makes the viewport bigger, can see more. 0084 m_dist = 5.3f; 0085 m_selected_number = 1; 0086 0087 loadSettings(); 0088 0089 m_isClicked = false; 0090 m_isRClicked = false; 0091 m_isDragging = false; 0092 0093 m_selection = -1; 0094 m_lastSelection = -1; 0095 m_highlights.fill(0, m_size); 0096 m_timeDelay = false; 0097 m_delayTimer = new QTimer(this); 0098 connect(m_delayTimer, &QTimer::timeout, this, &RoxdokuView::delayOver); 0099 } 0100 0101 RoxdokuView::~RoxdokuView() 0102 { 0103 glDeleteTextures(10, m_texture[0]); 0104 glDeleteTextures(25, m_texture[1]); 0105 } 0106 0107 void RoxdokuView::enterValue(int value) 0108 { 0109 if (m_selection >= 0) { 0110 m_game.setValue(m_selection, value); 0111 update(); 0112 } 0113 } 0114 0115 QString RoxdokuView::status() const 0116 { 0117 QString m; 0118 0119 // int secs = QTime(0,0).secsTo(m_game.time()); 0120 // if(secs % 36 < 12) 0121 // m = i18n("Selected item %1, Time elapsed %2. DRAG to rotate. MOUSE WHEEL to zoom in/out.", 0122 // m_symbols->value2Symbol(m_selected_number, m_game.order()), 0123 // m_game.time().toString("hh:mm:ss")); 0124 // else if(secs % 36 < 24) 0125 // m = i18n("Selected item %1, Time elapsed %2. DOUBLE CLICK on a cube to insert selected number.", 0126 // m_symbols->value2Symbol(m_selected_number, m_game.order()), 0127 // m_game.time().toString("hh:mm:ss")); 0128 // else 0129 // m = i18n("Selected item %1, Time elapsed %2. Type in a cell (zero to delete) to place that number in it.", 0130 // m_symbols->value2Symbol(m_selected_number, m_game.order()), 0131 // m_game.time().toString("hh:mm:ss")); 0132 0133 return m; 0134 } 0135 0136 0137 void RoxdokuView::initializeGL() 0138 { 0139 glClearColor( 0.0, 0.0, 0.0, 0.5 ); 0140 glEnable(GL_TEXTURE_2D); // Enable Texture Mapping ( NEW ) 0141 //glShadeModel(GL_SMOOTH); // Enable Smooth Shading 0142 //glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background 0143 //glClearDepth(1.0f); // Depth Buffer Setup 0144 glEnable(GL_DEPTH_TEST); // Enables Depth Testing 0145 //glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do 0146 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 0147 0148 setMouseTracking(true); 0149 0150 for(int o=0; o<2; o++) 0151 for(int i=0; i<=9+o*16; i++) 0152 { 0153 int sz = 64; 0154 QPixmap pic = Renderer::instance()->renderSpecial3D(SpecialCell, sz); 0155 if(i != 0) { 0156 pic = Renderer::instance()->renderSymbolOn(pic, i, 0, 9+o*16, SymbolPreset); 0157 } 0158 QImage pix = pic.toImage().mirrored(); 0159 0160 glGenTextures(1, &m_texture[o][i]); 0161 glBindTexture(GL_TEXTURE_2D, m_texture[o][i]); 0162 glTexImage2D(GL_TEXTURE_2D, 0,4, sz,sz, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*) pix.bits()); 0163 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering 0164 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering 0165 } 0166 } 0167 0168 void RoxdokuView::resizeGL(int w, int h ) { 0169 if (w == 0) w = 1; 0170 if (h == 0) h = 1; 0171 m_arcBall = new ArcBallT((GLfloat)w, (GLfloat)h); 0172 0173 glViewport(0, 0, (GLint)w, (GLint)h); 0174 glMatrixMode(GL_PROJECTION); // Select the Projection Matrix 0175 glLoadIdentity(); // Reset the Projection Matrix 0176 0177 gluPerspective(45.0f, (GLfloat)w / (GLfloat)h, 0.1f, 100.0f); 0178 0179 glMatrixMode(GL_MODELVIEW); // Select the Modelview Matrix 0180 glLoadIdentity(); 0181 } 0182 0183 0184 void RoxdokuView::mouseDoubleClickEvent ( QMouseEvent * /*e*/ ) 0185 { 0186 if(m_selection == -1) return; 0187 if(m_selected_number == -1) return; 0188 if(m_game.given(m_selection)) return; 0189 m_game.setValue(m_selection, m_selected_number); 0190 // update(); 0191 if(m_isDragging) releaseMouse(); 0192 } 0193 0194 void RoxdokuView::Selection(int mouse_x, int mouse_y) 0195 { 0196 if(m_isDragging) 0197 return; 0198 0199 makeCurrent(); 0200 0201 GLuint buffer[512]; 0202 GLint hits; 0203 0204 GLint viewport[4]; 0205 0206 glGetIntegerv(GL_VIEWPORT, viewport); 0207 glSelectBuffer(512, buffer); 0208 (void) glRenderMode(GL_SELECT); 0209 0210 glInitNames(); 0211 glPushName(0); 0212 0213 glMatrixMode(GL_PROJECTION); // Selects The Projection Matrix 0214 glPushMatrix(); // Push The Projection Matrix 0215 glLoadIdentity(); // Resets The Matrix 0216 0217 // This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is. 0218 gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport); 0219 gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f); 0220 glMatrixMode(GL_MODELVIEW); 0221 paintGL(); 0222 glMatrixMode(GL_PROJECTION); 0223 glPopMatrix(); 0224 glMatrixMode(GL_MODELVIEW); 0225 hits=glRenderMode(GL_RENDER); 0226 0227 if (hits > 0){ 0228 int choose = buffer[3]; 0229 int depth = buffer[1]; 0230 0231 for (int loop = 1; loop < hits; loop++){ 0232 // If This Object Is Closer To Us Than The One We Have Selected 0233 if (buffer[loop*4+1] < GLuint(depth)){ 0234 choose = buffer[loop*4+3]; 0235 depth = buffer[loop*4+1]; 0236 } 0237 } 0238 0239 if(choose <= m_size && choose > 0) 0240 m_selection = choose-1; 0241 0242 // Stop the timer if the selection is on a cube. 0243 if (m_timeDelay) { 0244 m_delayTimer->stop(); 0245 m_timeDelay = false; 0246 } 0247 setFocus(); 0248 paintGL(); 0249 } 0250 else if ((! m_timeDelay) && (m_selection != -1)) { 0251 // Avoid flickering when the pointer passes between cubes. 0252 m_delayTimer->start(300); 0253 m_timeDelay = true; 0254 } 0255 } 0256 0257 void RoxdokuView::delayOver() 0258 { 0259 // Remove the highlighting, etc. when the pointer rests between cubes. 0260 m_delayTimer->stop(); 0261 m_timeDelay = false; 0262 m_selection = -1; 0263 paintGL(); 0264 } 0265 0266 void RoxdokuView::mouseMoveEvent ( QMouseEvent * e ) 0267 { 0268 const int x = qRound(e->position().x()); 0269 const int y = qRound(e->position().y()); 0270 0271 Point2fT f; 0272 f.T[0] = x; 0273 f.T[1] = y; 0274 0275 Selection(x, y); 0276 0277 if (m_isRClicked){ // If Right Mouse Clicked, Reset All Rotations 0278 Matrix3fSetIdentity(&LastRot); // Reset Rotation 0279 Matrix3fSetIdentity(&ThisRot); // Reset Rotation 0280 Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Reset Rotation 0281 } 0282 0283 if (!m_isDragging){ // Not Dragging 0284 if (m_isClicked){ // First Click 0285 m_isDragging = true; // Prepare For Dragging 0286 LastRot = ThisRot; // Set Last Static Rotation To Last Dynamic One 0287 m_arcBall->click(&f); // Update Start Vector And Prepare For Dragging 0288 grabMouse(/*QCursor(Qt::SizeAllCursor)*/); 0289 } 0290 update(); 0291 } 0292 else{ 0293 if (m_isClicked){ // Still Clicked, So Still Dragging 0294 Quat4fT ThisQuat; 0295 0296 m_arcBall->drag(&f, &ThisQuat); // Update End Vector And Get Rotation As Quaternion 0297 Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // Convert Quaternion Into Matrix3fT 0298 Matrix3fMulMatrix3f(&ThisRot, &LastRot); // Accumulate Last Rotation Into This One 0299 Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Set Our Final Transform's Rotation From This One 0300 } 0301 else{ // No Longer Dragging 0302 m_isDragging = false; 0303 releaseMouse (); 0304 } 0305 update(); 0306 } 0307 } 0308 0309 void RoxdokuView::selectValue(int value) { 0310 m_selected_number = value; 0311 } 0312 0313 void RoxdokuView::loadSettings() { 0314 m_guidedMode = Settings::showErrors(); 0315 m_showHighlights = Settings::showHighlights3D(); 0316 0317 float s = Settings::overallSize3D()/10.0f; // Normal size. 0318 m_unhighlightedSize = s; 0319 m_selectionSize = s * Settings::selectionSize3D()/10.0f; 0320 m_highlightedSize = s * Settings::highlightedSize3D()/10.0f; 0321 m_outerCellSize = s * Settings::outerCellSize3D()/10.0f; 0322 m_darkenOuterCells = Settings::darkenOuterCells3D(); 0323 } 0324 0325 void RoxdokuView::settingsChanged() { 0326 loadSettings(); 0327 update(); 0328 } 0329 0330 void RoxdokuView::myDrawCube(bool highlight, int name, 0331 GLfloat x, GLfloat y, GLfloat z, bool outside) 0332 { 0333 glPushMatrix(); 0334 glLoadName(name+1); 0335 glTranslatef(x,y,z); 0336 0337 glBindTexture(GL_TEXTURE_2D, m_texture[m_order >= 16][m_game.value(name)]); 0338 0339 float sz = 1.0f; 0340 float s = 0.2f; 0341 if(m_selection != -1 && m_selection != name && highlight) { 0342 s = +0.2; 0343 sz = m_highlightedSize; 0344 0345 switch(m_game.buttonState(name)) { 0346 case ksudoku::GivenValue: 0347 glColor3f(0.85f,1.0f,0.4f); // Green/Gold. 0348 break; 0349 case ksudoku::ObviouslyWrong: 0350 case ksudoku::WrongValue: 0351 if(m_guidedMode && m_game.puzzle()->hasSolution()) 0352 glColor3f(0.75f,0.25f,0.25f); // Red. 0353 else 0354 glColor3f(0.75f+s,0.75f+s,0.25f+s); 0355 break; 0356 case ksudoku::Marker: 0357 case ksudoku::CorrectValue: 0358 glColor3f(0.75f+s,0.75f+s,0.25f+s); // Gold. 0359 break; 0360 } 0361 } else { 0362 sz = m_unhighlightedSize; 0363 s = 0.1f; 0364 if (outside && (m_selection != -1)) { 0365 // Shrink and darken cells outside the selection-volume. 0366 sz = m_outerCellSize; 0367 s = m_darkenOuterCells ? -0.24f : 0.0f; 0368 } 0369 switch(m_game.buttonState(name)) { 0370 case ksudoku::GivenValue: 0371 glColor3f(0.6f+s,0.9f+s,0.6f+s); // Green. 0372 break; 0373 case ksudoku::ObviouslyWrong: 0374 case ksudoku::WrongValue: 0375 if(m_guidedMode && m_game.puzzle()->hasSolution()) 0376 glColor3f(0.75f,0.25f,0.25f); // Red. 0377 else 0378 glColor3f(0.6f+s,1.0f+s,1.0f+s);// Blue. 0379 break; 0380 case ksudoku::Marker: 0381 case ksudoku::CorrectValue: 0382 glColor3f(0.6f+s,1.0f+s,1.0f+s); // Blue. 0383 break; 0384 } 0385 } 0386 0387 if(m_selection == name) { 0388 sz = m_selectionSize; 0389 // IDW test. glColor3f(0.75f,0.25f,0.25f); 0390 glColor3f(1.0f,0.8f,0.4f); // Orange. 0391 } 0392 0393 glBegin(GL_QUADS); 0394 /* front face */ 0395 glTexCoord2f(0.0f, 0.0f); 0396 glVertex3f(-sz, -sz, sz); 0397 glTexCoord2f(1.0f, 0.0f); 0398 glVertex3f(sz, -sz, sz); 0399 glTexCoord2f(1.0f, 1.0f); 0400 glVertex3f(sz, sz, sz); 0401 glTexCoord2f(0.0f, 1.0f); 0402 glVertex3f(-sz, sz, sz); 0403 /* back face */ 0404 glTexCoord2f(1.0f, 0.0f); 0405 glVertex3f(-sz, -sz, -sz); 0406 glTexCoord2f(1.0f, 1.0f); 0407 glVertex3f(-sz, sz, -sz); 0408 glTexCoord2f(0.0f, 1.0f); 0409 glVertex3f(sz, sz, -sz); 0410 glTexCoord2f(0.0f, 0.0f); 0411 glVertex3f(sz, -sz, -sz); 0412 /* right face */ 0413 glTexCoord2f(1.0f, 0.0f); 0414 glVertex3f(sz, -sz, -sz); 0415 glTexCoord2f(1.0f, 1.0f); 0416 glVertex3f(sz, sz, -sz); 0417 glTexCoord2f(0.0f, 1.0f); 0418 glVertex3f(sz, sz, sz); 0419 glTexCoord2f(0.0f, 0.0f); 0420 glVertex3f(sz, -sz, sz); 0421 /* left face */ 0422 glTexCoord2f(1.0f, 0.0f); 0423 glVertex3f(-sz, -sz, sz); 0424 glTexCoord2f(1.0f, 1.0f); 0425 glVertex3f(-sz, sz, sz); 0426 glTexCoord2f(0.0f, 1.0f); 0427 glVertex3f(-sz, sz, -sz); 0428 glTexCoord2f(0.0f, 0.0f); 0429 glVertex3f(-sz, -sz, -sz); 0430 /* top face */ 0431 glTexCoord2f(1.0f, 0.0f); 0432 glVertex3f(sz, sz, sz); 0433 glTexCoord2f(1.0f, 1.0f); 0434 glVertex3f(sz, sz, -sz); 0435 glTexCoord2f(0.0f, 1.0f); 0436 glVertex3f(-sz, sz, -sz); 0437 glTexCoord2f(0.0f, 0.0f); 0438 glVertex3f(-sz, sz, sz); 0439 /* bottom face */ 0440 glTexCoord2f(1.0f, 0.0f); 0441 glVertex3f(sz, -sz, -sz); 0442 glTexCoord2f(1.0f, 1.0f); 0443 glVertex3f(sz, -sz, sz); 0444 glTexCoord2f(0.0f, 1.0f); 0445 glVertex3f(-sz, -sz, sz); 0446 glTexCoord2f(0.0f, 0.0f); 0447 glVertex3f(-sz, -sz, -sz); 0448 glEnd(); 0449 glPopMatrix(); 0450 } 0451 0452 void RoxdokuView::paintGL() 0453 { 0454 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 0455 glLoadIdentity(); 0456 0457 glLightfv(GL_LIGHT1, GL_POSITION,LightPosition); 0458 glTranslatef(0.0f, 0.0f, -m_dist*(m_width+3)+m_wheelmove); 0459 0460 glMultMatrixf(Transform.M); 0461 0462 enum {Outside, Inside, Highlight}; 0463 int selX = -1, selY = -1, selZ = -1; 0464 0465 // If a cell is newly selected work out the highlights and lowlights. 0466 if ((m_selection != -1) && (m_selection != m_lastSelection)) { 0467 m_lastSelection = m_selection; 0468 selX = m_graph->cellPosX (m_selection); 0469 selY = m_graph->cellPosY (m_selection); 0470 selZ = m_graph->cellPosZ (m_selection); 0471 0472 // Note: m_highlights persists through many frame-paints per second. 0473 m_highlights.fill(Outside, m_size); 0474 0475 // Mark the cells to be highlighted when highlighting is on. 0476 QList<int> groupsToHighlight = m_graph->cliqueList(m_selection); 0477 for(int g = 0; g < groupsToHighlight.count(); g++) { 0478 QList<int> cellList = 0479 m_graph->clique(groupsToHighlight.at(g)); 0480 for (int n = 0; n < m_order; n++) { 0481 m_highlights[cellList.at(n)] = Highlight; 0482 } 0483 } 0484 0485 // Mark non-highlighted cells that are inside cubes containing 0486 // the selected cell. In custom Roxdoku puzzles with > 1 cube, 0487 // cells outside are shrunk and darkened. 0488 for (int n = 0; n < m_graph->structureCount(); n++) { 0489 int cubePos = m_graph->structurePosition(n); 0490 int cubeX = m_graph->cellPosX(cubePos); 0491 int cubeY = m_graph->cellPosY(cubePos); 0492 int cubeZ = m_graph->cellPosZ(cubePos); 0493 if (m_graph->structureType(n) != SKGraph::RoxdokuGroups) { 0494 continue; 0495 } 0496 if ((selX >= cubeX) && (selX < (cubeX + m_base)) && 0497 (selY >= cubeY) && (selY < (cubeY + m_base)) && 0498 (selZ >= cubeZ) && (selZ < (cubeZ + m_base))) { 0499 for (int x = cubeX; x < cubeX + m_base; x++) { 0500 for (int y = cubeY; y < cubeY + m_base; y++) { 0501 for (int z = cubeZ; z < cubeZ + m_base; z++) { 0502 int pos = m_graph->cellIndex(x, y, z); 0503 if (m_highlights.at(pos) == Outside) { 0504 m_highlights[pos] = Inside; 0505 } 0506 } 0507 } 0508 } 0509 } 0510 } 0511 } 0512 0513 int c = 0; 0514 0515 for(int xx = 0; xx < m_width; ++xx) { 0516 for(int yy = 0; yy < m_height; ++yy) { 0517 for(int zz = 0; zz < m_depth; ++zz) { 0518 if(m_game.value(c) == UNUSABLE) { 0519 c++; 0520 continue; // Do not paint unusable cells. 0521 } 0522 glPushMatrix(); 0523 0524 // Centre the puzzle in the viewport. 0525 glTranslatef(-(m_dist * m_width - m_dist) / 2, 0526 -(m_dist * m_height - m_dist) / 2, 0527 -(m_dist * m_depth - m_dist) / 2); 0528 0529 // Highlight cells in the three planes through 0530 // the selected cell. Unhighlight cells outside 0531 // the cubical volume of the selection. 0532 bool highlight = m_showHighlights && 0533 (m_highlights.at(c) == Highlight); 0534 bool outside = (m_highlights.at(c) == Outside); 0535 0536 myDrawCube(highlight, c++, 0537 (GLfloat)(m_dist * xx), 0538 (GLfloat)(m_dist * yy), 0539 (GLfloat)(m_dist * zz), outside); 0540 0541 glPopMatrix(); 0542 } 0543 } 0544 } 0545 } 0546 0547 } 0548 0549 #include "moc_roxdokuview.cpp"