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"