File indexing completed on 2024-04-21 04:05:09

0001 /*
0002     SPDX-FileCopyrightText: 2008 Ian Wadham <iandw.au@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "movetracker.h"
0008 
0009 #include <math.h>
0010 #include <stdlib.h>
0011 #include <stdio.h>
0012 #include <QWidget>
0013 #include "kubrick_debug.h"
0014 
0015 MoveTracker::MoveTracker (QWidget * parent)
0016     :
0017     QObject (parent),
0018     myParent (parent)
0019 {
0020     init();
0021     rotationState.quaternionSetIdentity();
0022     rotationState.quaternionToMatrix (rotationMatrix);
0023 }
0024 
0025 
0026 MoveTracker::~MoveTracker()
0027 {
0028 }
0029 
0030 
0031 void MoveTracker::init()
0032 {
0033     currentButton = Qt::NoButton;   // No mouse button being pressed.
0034     clickFace1    = false;      // No face clicked by mouse button.
0035     foundFace1    = false;      // Starting face of move not found.
0036     foundFace2    = false;      // Finishing face of move not found.
0037     foundHandle   = false;      // Rotation-handle not found.
0038     moveAngle     = 0;          // No slice-move to be made (yet).
0039 }
0040 
0041 void MoveTracker::saveSceneInfo()
0042 {
0043     glGetDoublev (GL_PROJECTION_MATRIX, projectionMatrix);
0044     glGetDoublev (GL_MODELVIEW_MATRIX, modelViewMatrix);
0045     glGetIntegerv (GL_VIEWPORT, viewPort);
0046 }
0047 
0048 void MoveTracker::mouseInput (int sceneID, const QList<CubeView *> &cubeViews,
0049         Cube * cube, MouseEvent event, int button, int mX, int mY)
0050 {
0051     if (event == ButtonDown) {
0052     init();
0053     currentButton = button;
0054     }
0055 
0056     if (currentButton == Qt::RightButton) {
0057     // Right-button is down: rotate the whole cube.
0058     trackCubeRotation (sceneID, cubeViews, event, mX, mY);
0059     }
0060     else if (currentButton == Qt::LeftButton) {
0061     // Left-button is down: move a slice of the cube.
0062     trackSliceMove (sceneID, cubeViews, cube, event, mX, mY);
0063     }
0064 
0065     if (event == ButtonUp) {
0066     currentButton = Qt::NoButton;
0067     }
0068 }
0069 
0070 
0071 void MoveTracker::trackCubeRotation (int sceneID, const QList<CubeView *> &cubeViews,
0072         MouseEvent event, int mX, int mY)
0073 {
0074     if (foundHandle) {
0075     // Move the handle-point to a new position while rotating the cube
0076     // around its central point.  The mouse-pointer appears to be attached
0077     // to the handle-point as the cube rotates.
0078 
0079     if ((mX == mX1) && (mY == mY1)) {
0080         return;     // No change in position of mouse.
0081     }
0082     mX1 = mX;
0083     mY1 = mY;
0084 
0085     double axis [nAxes] = {1.0, 0.0, 0.0};
0086     double degrees = 0.0;
0087 
0088     // Calculate the angle and axis of rotation.
0089 
0090     // If the angle was effectively zero, avoid further calculation.
0091     if (calculateRotation (mX, mY, axis, degrees)) {
0092         // Else, add the rotation to the quaternion and the OpenGL matrix.
0093         rotationState.quaternionAddRotation (axis, degrees);
0094         rotationState.quaternionToMatrix (rotationMatrix);
0095         Q_EMIT cubeRotated ();
0096     }
0097     }
0098     else if (event != ButtonUp) {
0099     // Look for a handle-point: a point on the surface of a cube that can
0100     // be used to rotate the cube around its central point.  Any point will
0101     // do, provided it is visible and on the surface of a cube.
0102 
0103     GLfloat depth;
0104     double position [nAxes];
0105 
0106     // Get the mouse position in OpenGL world co-ordinates.
0107     depth = getMousePosition (mX, mY, position);
0108 
0109     // Find which picture of a cube the mouse is on.
0110     int cubeID = findWhichCube (sceneID, cubeViews, position);
0111     if (cubeID < 0) {
0112         // Could not find the nearest cube (should never happen).
0113         return;
0114     }
0115     v = cubeViews.at (cubeID);
0116 
0117     if (position[Z] > (-maxZ + 0.1)) {
0118         // Get the mouse position relative to the centre of the cube found.
0119         // Use the transformation matrix for the translated and standardly
0120         // aligned view of that cube, without including any user's rotation.
0121 
0122         getGLPosition (mX, mY, depth, v->matrix0, handle);
0123         RR = handle[X]*handle[X] + handle[Y]*handle[Y] +
0124                     handle[Z]*handle[Z];
0125         R = sqrt (RR);
0126         foundHandle = true;
0127         mX1 = mX;
0128         mY1 = mY;
0129     }
0130     }
0131 }
0132 
0133 
0134 bool MoveTracker::calculateRotation (const int mX, const int mY,
0135                     double axis[], double & degrees)
0136 {
0137     bool result = true;
0138 
0139     // Get two points on the line of sight, in the nearest cube's co-ordinates.
0140     // Use the transformation matrix for the translated and standardly aligned
0141     // view of that cube, without including any user's rotation.
0142 
0143     double p1 [nAxes];
0144     double p2 [nAxes];
0145 
0146     // The "depth" in OpenGL is a normalised number in the range 0.0 to 1.0.
0147     // We choose values 0.5 and 1.0 (background) for the two depths.
0148 
0149     getGLPosition (mX, mY, 0.5, v->matrix0, p1);
0150     getGLPosition (mX, mY, 1.0, v->matrix0, p2);
0151 
0152     // Find where the line of sight intersects the sphere containing the handle.
0153     // To do this, we use the parametrised equation of a line between two
0154     // points, i.e.  p = p1 + lambda * (p2 - p1) for any point p.  At those two
0155     // points, px*px + py*py + pz*pz = R*R, which gives us a quadratic equation
0156     // to solve for lambda: a*lambda*lambda + b*lambda +c = 0.  So we calculate
0157     // the coefficents a, b, and c.
0158 
0159     double dx = (p2[X] - p1[X]);
0160     double dy = (p2[Y] - p1[Y]);
0161     double dz = (p2[Z] - p1[Z]);
0162     double a  = dx*dx + dy*dy + dz*dz;
0163     double b  = 2.0*(p1[X]*dx + p1[Y]*dy + p1[Z]*dz);
0164     double c  = p1[X]*p1[X] + p1[Y]*p1[Y] + p1[Z]*p1[Z] - RR;
0165 
0166     // Apply the quadratic formula to solve for the nearest of the two lambdas.
0167     double q  = b*b - 4.0*a*c;
0168     double lambda = 0.0;
0169     if (q >= 0.0) {
0170     lambda = (-b - sqrt (q)) / (2.0*a);
0171     }
0172     else {
0173     // The line of sight to the mouse pointer is outside the handle-sphere.
0174     // The sphere must not turn any more, otherwise it flips unpredictably.
0175     degrees = 0.0;
0176     axis [X] = 1.0;
0177     axis [Y] = 0.0;
0178     axis [Z] = 0.0;
0179     return false;
0180     }
0181 
0182     // Set up a vector for the old position on the handle-sphere.
0183     double v1 [nAxes];
0184 
0185     v1 [X] = handle [X];
0186     v1 [Y] = handle [Y];
0187     v1 [Z] = handle [Z];
0188 
0189     // Set the new handle position on the handle-sphere.
0190     handle [X] = (p1[X] + lambda*dx);
0191     handle [Y] = (p1[Y] + lambda*dy);
0192     handle [Z] = (p1[Z] + lambda*dz);
0193 
0194     result = getTurnVector (v1, handle, axis, degrees);
0195     return result;
0196 }
0197 
0198 
0199 void MoveTracker::trackSliceMove (int sceneID, const QList<CubeView *> &cubeViews,
0200         Cube * cube, MouseEvent event, int mX, int mY)
0201 {
0202     double position [nAxes];
0203 
0204     if ((foundHandle) && ((mX != mX1) || (mY != mY1))) {
0205     mX1 = mX;
0206     mY1 = mY;
0207 
0208     // Change the move axis and direction only when the mouse pointer moves
0209     // away from the handle area or after it returns to it and moves away
0210     // again.  This prevents rapid switching and oscillation of the slices
0211     // when the pointer is in a diagonal direction from the handle-point.
0212 
0213     // IDW bool outsideHandleArea = (abs (mX - mX0) > 15) || (abs (mY - mY0) > 15);
0214     bool outsideHandleArea = ((mX - mX0)*(mX - mX0) + (mY - mY0)*(mY - mY0))
0215                 > 400;
0216 
0217     if ((moveAngle == 0) && outsideHandleArea) {
0218         // Mouse just moved outside handle area: find the required move.
0219         Axis   direction = X;
0220         double distance = 0.0;
0221 
0222         // Get two points on line of sight, in the nearest cube's co-ords.
0223         // Use the transformation matrix for the fully translated and
0224         // rotated view of that cube, including any user's rotations.
0225 
0226         double point1 [nAxes];
0227         double point2 [nAxes];
0228 
0229         // The "depth" in OpenGL is a normalised number in the range 0.0 to
0230         // 1.0, so use values 0.5 and 1.0 (background) for the two depths.
0231 
0232         getGLPosition (mX, mY, 0.5, v->matrix, point1);
0233         getGLPosition (mX, mY, 1.0, v->matrix, point2);
0234 
0235         // Find where the line of sight intersects the plane of the found
0236         // face.  To do this, use the parametrised equation of a line
0237         // between two points: p = p2 + lambda * (p1 - p2) for any point p.
0238 
0239         double plane  = handle[noTurn];
0240         double lambda = (plane - point2[noTurn]) /
0241                                 (point1[noTurn] - point2[noTurn]);
0242         LOOP (n, nAxes) {
0243         if (n == noTurn) {
0244             position[n] = plane;
0245         }
0246         else {
0247             position[n] = point2[n] + lambda * (point1[n] - point2[n]);
0248         }
0249         }
0250 
0251         // Find in which direction the mouse moved most.
0252         LOOP (n, nAxes) {
0253         if (n != noTurn) {
0254             double d = position[n] - handle[n];
0255             if (fabs (d) > fabs (distance)) {
0256             distance = d;
0257             direction = (Axis) n;
0258             }
0259         }
0260         }
0261 
0262         // Turn axis will be at right angles to mouse move and face-normal.
0263         currentMoveAxis = (((direction + 1) % nAxes) == noTurn) ?
0264                 (Axis) ((direction + 2) % nAxes) :
0265                 (Axis) ((direction + 1) % nAxes);
0266 
0267         // Set up unit vectors for the mouse move and the face-normal.
0268         int moveVec[nAxes] = {0, 0, 0};
0269         int faceVec[nAxes] = {0, 0, 0};
0270         moveVec[direction] = (distance < 0.0) ? -1 : +1;
0271         faceVec[noTurn]    = (face1[noTurn] < 0.0) ? -1 : +1;
0272 
0273         // Form a partial cross-product to get the direction of the turn.
0274         // The other components of turnVec must be zero (3 orthogonal axes).
0275 
0276         int a = (currentMoveAxis + 1) % nAxes;  // Get non-turn axes in
0277         int b = (currentMoveAxis + 2) % nAxes;  // cyclical order.
0278         int turnVec = moveVec[a]*faceVec[b] - moveVec[b]*faceVec[a];
0279 
0280         currentMoveDirection = (turnVec < 0) ? ANTICLOCKWISE : CLOCKWISE;
0281         moveAngle            = (turnVec < 0) ? -6 : 6;
0282 
0283         currentMoveSlice     = face1[currentMoveAxis];
0284         cube->setMoveInProgress (currentMoveAxis, currentMoveSlice);
0285     }
0286     else if (! outsideHandleArea) {
0287         moveAngle = 0;
0288     }
0289 
0290     cube->setMoveAngle (moveAngle); // If zero, no move will be triggered.
0291     }
0292 
0293     // Start by looking for a handle-point from which to make a slice move.
0294     else if ((! foundHandle) && (event != ButtonUp)) {
0295     // Get the mouse position in OpenGL world co-ordinates.
0296     GLfloat depth;
0297     depth = getMousePosition (mX, mY, position);
0298 
0299     // Continue only if the mouse hit a cube, not the background.
0300     if (position[Z] > (-maxZ + 0.1)) {
0301 
0302         // Find which picture of a cube the mouse is on.
0303         int cubeID = findWhichCube (sceneID, cubeViews, position);
0304         if (cubeID < 0) {
0305         // Could not find the nearest cube (should never happen).
0306         return;
0307         }
0308         v = cubeViews.at (cubeID);
0309 
0310         // Get the mouse position relative to the centre of the cube found.
0311         // Use the transformation matrix for the fully translated and
0312         // rotated view of that cube, including any user's rotations.
0313 
0314         getGLPosition (mX, mY, depth, v->matrix, handle);
0315 
0316         // Find the sticker at that (handle) position.
0317         if (! cube->findSticker (handle, v->cubieSize, face1)) {
0318         // Could not find the sticker (should never happen).
0319         return;
0320         }
0321 
0322         // Find the axis that is NOT a possible axis of the slice move.
0323         noTurn = cube->faceNormal (face1);
0324         foundHandle = true;
0325 
0326         // Save the mouse-position for future tracking and comparison.
0327         mX0 = mX; mY0 = mY;
0328         mX1 = mX; mY1 = mY;
0329     }
0330     }
0331 
0332     // After a button-release, perform the slice move required (if any).
0333     if (event == ButtonUp) {
0334     if (moveAngle != 0) {
0335         // We found a move.
0336         Move * move          = new Move;
0337         move->axis           = currentMoveAxis;
0338         move->slice          = currentMoveSlice;
0339         move->direction      = currentMoveDirection;
0340 
0341         Q_EMIT newMove (move);  // Signal Game obj to store this move.
0342     }
0343     moveAngle = 0;
0344     cube->setMoveAngle (0);
0345     }
0346 }
0347 
0348 
0349 void MoveTracker::realignCube (QList<Move *> & tempMoves)
0350 {
0351     double fromAxis [nAxes * nAxes] = {1.0, 0.0, 0.0,   // X axis.
0352                                        0.0, 1.0, 0.0,   // Y axis.
0353                                        0.0, 0.0, 1.0};  // Z axis.
0354     double toAxis   [nAxes * nAxes] = {1.0, 0.0, 0.0,   // X axis.
0355                                        0.0, 1.0, 0.0,   // Y axis.
0356                                        0.0, 0.0, 1.0};  // Z axis.
0357 
0358     // Find where the original X, Y and Z axes have ended up.
0359     rotateAxes (rotationState, fromAxis, toAxis);
0360 
0361     // Find which component of which axis is closest to X, Y, Z, -X, -Y or -Z.
0362     int bestAligned = -1;
0363     double component = 0.0;
0364     LOOP (n, nAxes) {
0365     int k = n * nAxes;
0366     LOOP (m, nAxes) {
0367         // Pick the largest component as the best aligned.
0368         if (fabs(toAxis [k + m]) > component) {
0369         bestAligned = k + m;
0370         component = fabs(toAxis [k + m]);
0371         }
0372     }
0373     }
0374 
0375     // Determine which axis (X, -X, etc.) is closest to the best-aligned one.
0376     int nAxisTo = bestAligned % nAxes;
0377 
0378     double v1 [nAxes] = {0.0, 0.0, 0.0};    // The first axis to be aligned.
0379     double v2 [nAxes] = {0.0, 0.0, 0.0};    // The axis to align it to.
0380 
0381     int offset = bestAligned - nAxisTo;
0382     LOOP (n, nAxes) {
0383     v1 [n] = toAxis [offset + n];
0384     }
0385 
0386     // Determine whether the alignment will be parallel or anti-parallel.
0387     v2 [nAxisTo] = toAxis [bestAligned] < 0.0 ? -1.0 : 1.0;
0388 
0389     // Calculate the turn required to align the best-aligned axis exactly.
0390     double turnVector [nAxes] = {1.0, 0.0, 0.0};
0391     double turnAngle = 0.0;
0392     if (getTurnVector (v1, v2, turnVector, turnAngle)) {
0393     // Apply the required turn to the cube images (if non-zero).
0394     rotationState.quaternionAddRotation (turnVector, turnAngle);
0395     rotationState.quaternionToMatrix (rotationMatrix);
0396     }
0397 
0398     // Find where the original X, Y and Z axes are now.
0399     rotateAxes (rotationState, fromAxis, toAxis);
0400 
0401     // The remaining two axes must rotate in the plane perpendicular to the axis
0402     // to which the first axis was aligned.  Once one of the two axes is in
0403     // place, the other axis will automatically follow.
0404 
0405     double v3 [nAxes] = {0.0, 0.0, 0.0};    // The next axis to be aligned.
0406     double v4 [nAxes] = {0.0, 0.0, 0.0};    // The axis with which to align.
0407 
0408     // Pick the next axis to be aligned (in cyclical order).
0409     int nAxis2 = ((bestAligned / nAxes) + 1) % nAxes;
0410     v3 [nAxis2] = 1.0;
0411 
0412     // Find where that axis is now, after the first alignment.
0413     rotationState.quaternionRotateVector (v3);
0414 
0415     // Find which axis is the closest axis one to align to.
0416     component = 0.0;
0417     nAxisTo = 0;
0418     LOOP (n, nAxes) {
0419     if (fabs (v3 [n]) > component) {
0420         component = fabs (v3 [n]);
0421         nAxisTo = n;
0422     }
0423     }
0424 
0425     // Determine whether the alignment will be parallel or anti-parallel.
0426     v4 [nAxisTo] = v3 [nAxisTo] < 0.0 ? -1.0 : 1.0;
0427 
0428     // Calculate the turn required to align the two remaining axes exactly.
0429     if (getTurnVector (v3, v4, turnVector, turnAngle)) {
0430     // Apply the required turn to the cube images (if non-zero).
0431     rotationState.quaternionAddRotation (turnVector, turnAngle);
0432     rotationState.quaternionToMatrix (rotationMatrix);
0433     }
0434 
0435     // The original X, Y and Z axes should now be aligned, probably with other
0436     // axes.  So find out what is aligned with what and make the 90 or 180
0437     // degree moves of the underlying cube needed to get to that position.
0438 
0439     rotateAxes (rotationState, fromAxis, toAxis);
0440     makeWholeCubeMoveList (tempMoves, toAxis);
0441 
0442     // Finally set the cube images to the unrotated state.
0443     rotationState.quaternionSetIdentity();
0444     rotationState.quaternionToMatrix (rotationMatrix);
0445 }
0446 
0447 
0448 void MoveTracker::makeWholeCubeMoveList (QList<Move *> & tempMoves,
0449             const double to [nAxes * nAxes])
0450 {
0451     // The cube has been rotated by the player and aligned to the standard
0452     // orientation (top, front and right faces visible), however it will
0453     // probably not be in its original orientation.  This procedure works
0454     // out a series of 90 and 180 degree moves that will get back there.
0455     // The reverse of these moves is added to the player's list of moves
0456     // and applied to the internal Cube object, so that the player's manual
0457     // rotations are replaced by the equivalent 90 and 180 degree Cube moves.
0458     //
0459     // This makes keyboard and Singmaster-notation moves once again meaningful.
0460     // For example, the Y-axis is again the one pointing up and T (top) again
0461     // represents the top face of the cube, as seen by the player.
0462 
0463     int  toI   [nAxes] [nAxes] = {{0, 0, 0},
0464                                   {0, 0, 0},
0465                                   {0, 0, 0}};
0466     bool notAligned = true;
0467     int  safetyLimit = 3;
0468 
0469     LOOP (row, nAxes) {
0470     LOOP (col, nAxes) {
0471         if (fabs (to [row * nAxes + col]) > 0.999) {
0472         toI [row] [col] = (to [row * nAxes + col] < 0.0) ? -1 : +1;
0473         }
0474     }
0475     }
0476 
0477     while (notAligned) {
0478     if (--safetyLimit <= 0) notAligned = false;
0479     // Determine whether any axes are fully aligned.
0480     if ((toI [X][X] == 1) || (toI [Y][Y] == 1) || (toI [Z][Z] == 1)) {
0481         // At least one axis is fully aligned.
0482         if ((toI [X][X] == 1) && (toI [Y][Y] == 1) && (toI [Z][Z] == 1)) {
0483         // All axes are fully aligned: time to stop.
0484         notAligned = false;
0485         break;
0486         }
0487         // Find which axis is fully aligned and what move aligns the others.
0488         else {
0489         int aligned  = -1;
0490         int reversed = -1;
0491         LOOP (n, nAxes) {
0492             if (toI [n][n] == -1) {
0493             // This axis is reversed.
0494             reversed = n;
0495             }
0496             if (toI [n][n] == 1) {
0497             // This axis is fully aligned.
0498             aligned = n;
0499             }
0500         }
0501         if (aligned < 0) {
0502             // Should never happen ...
0503         }
0504         // Calculate whether to rotate 90, -90 or 180 degrees.
0505         Axis a = (Axis) aligned;
0506         if (reversed >= 0) {
0507             // One axis is aligned and both of the others are reversed.
0508             // A single 180 degree move should bring all axes into line.
0509 
0510             prepareWholeCubeMove (tempMoves, toI, a, ONE_EIGHTY);
0511         }
0512         else {
0513             // A single 90 degree move should bring all axes into line.
0514 
0515             // Calculate the required rotation around axis "a" from the
0516             // positions of the other two axes (n1 and n2).  For example
0517             // if a is the Y axis, then n1 is the Z axis and n2 is the
0518             // X axis (in cyclical order), so the Y axis (n1) is either
0519             // pointing to +Z or -Z, as represented by (toI [Y][Z]).
0520 
0521             Axis     n1 = (Axis) ((a + 1) % nAxes);
0522             Axis     n2 = (Axis) ((a + 2) % nAxes);
0523             prepareWholeCubeMove (tempMoves, toI, a,
0524                 (toI [n1][n2] > 0) ? CLOCKWISE : ANTICLOCKWISE);
0525         }
0526         }
0527     }
0528     // No axes are fully aligned: pick one to align.
0529     else {
0530         int reversed = -1;
0531         LOOP (n, nAxes) {
0532         if (toI [n][n] == -1) {
0533             // This axis is reversed.
0534             reversed = n;
0535             break;
0536         }
0537         }
0538         if (reversed >= 0) {
0539         // Pick an axis that is not reversed and rotate it 180 degrees.
0540         // That should bring one axis into line.
0541 
0542         prepareWholeCubeMove (tempMoves, toI,
0543             (Axis) ((reversed + 1) % nAxes), ONE_EIGHTY);
0544         }
0545         else {
0546         // Pick any axis and rotate it 90 or -90 degrees to align it.
0547         // If X is parallel/anti-parallel to the Z axis, we need to
0548         // rotate around the Y axis and vice-versa.
0549         Axis     rAxis = (toI [X][Y] == 0) ? Y : Z;
0550         Rotation r     = CLOCKWISE;
0551         r = (((rAxis == Y) && (toI [X][Z] > 0)) ||  // If X is at +Z
0552              ((rAxis == Z) && (toI [X][Y] < 0))) ?  // or -Y, turn
0553             ANTICLOCKWISE : r;          // anti-clock.
0554         prepareWholeCubeMove (tempMoves, toI, rAxis, r);
0555         }
0556     }
0557     }
0558 }
0559 
0560 
0561 void MoveTracker::prepareWholeCubeMove (QList<Move *> & moveList,
0562             int to [nAxes][nAxes], const Axis a, const Rotation d)
0563 {
0564     int dir   = (d == ANTICLOCKWISE) ? -1 : +1;
0565     int count = (d == ONE_EIGHTY)    ? 2  : 1;
0566 
0567     Axis coord1 = (Axis) ((a + 1) % nAxes);
0568     Axis coord2 = (Axis) ((a + 2) % nAxes);
0569     int  temp;
0570 
0571     LOOP (n, count) {
0572     LOOP (i, nAxes) {
0573         temp = to [i][coord1];
0574         to [i][coord1] = +dir * to [i][coord2];
0575         to [i][coord2] = -dir * temp;
0576     }
0577     }
0578 
0579     Move * move          = new Move;
0580     move->axis           = a;
0581     move->slice          = WHOLE_CUBE;
0582     move->direction      = CLOCKWISE;
0583     move->degrees        = 90;
0584 
0585     // Stack the moves onto the list in reverse order.
0586 
0587     if (d == ONE_EIGHTY) {
0588     // Use two 90 degree moves (making the equivalent Singmaster cube-moves
0589     // easier to process later on in undo/redo operations, etc.).
0590 
0591     moveList.prepend (move);
0592 
0593     move             = new Move;        // Allocate new heap space.
0594     move->axis       = a;
0595     move->slice      = WHOLE_CUBE;
0596     move->direction  = CLOCKWISE;
0597     move->degrees    = 90;
0598 
0599     moveList.prepend (move);
0600     }
0601     else {
0602     // Reverse a 90 degree move.
0603     move->direction  = (d == CLOCKWISE) ? ANTICLOCKWISE : CLOCKWISE;
0604 
0605     moveList.prepend (move);
0606     }
0607 }
0608 
0609 
0610 int MoveTracker::findWhichCube (const int sceneID,
0611         const QList<CubeView *> &cubeViews, const double position[])
0612 {
0613     // For some reason this function cannot compile with return-type CubeView *.
0614 
0615     double distance = 10000.0;          // Large value.
0616     double d        = 0.0;
0617     double dx       = 0.0;
0618     double dy       = 0.0;
0619     double dz       = 0.0;
0620     int    indexV   = -1;
0621 
0622     // Find which cube in the current scene is closest to the given position.
0623     LOOP (n, cubeViews.size()) {
0624         CubeView * v = cubeViews.at (n);
0625     if (v->sceneID != sceneID) {
0626         continue;               // Skip unwanted scene IDs.
0627     }
0628 
0629     dx = position[X] - v->position[X];
0630     dy = position[Y] - v->position[Y];
0631     dz = position[Z] - v->position[Z];
0632     d  = sqrt (dx*dx + dy*dy + dz*dz);  // Pythagoras.
0633     if (d < distance) {
0634         distance = d;
0635         indexV   = n;
0636     }
0637     }
0638     return (indexV);
0639 }
0640 
0641 
0642 void MoveTracker::rotateAxes (const Quaternion & r,
0643         const double from [nAxes * nAxes], double to [nAxes * nAxes])
0644 {
0645     LOOP (n, nAxes) {
0646     int k = n * nAxes;
0647     LOOP (m, nAxes) {
0648         to [k + m] = from [k + m];      // Copy the input vector.
0649     }
0650     r.quaternionRotateVector (&(to [k]));   // Rotate it.
0651     }
0652 }
0653 
0654 
0655 bool MoveTracker::getTurnVector (
0656         const double v1 [nAxes], const double v2 [nAxes],
0657         double turnAxis [nAxes], double & turnAngle)
0658 {
0659     double radius;
0660     double u1 [nAxes], u2 [nAxes];
0661     bool result = true;
0662 
0663     // Make v1 and v2 into unit vectors.
0664     radius = sqrt (v1[X]*v1[X] + v1[Y]*v1[Y] + v1[Z]*v1[Z]);
0665     u1[X] = v1[X] / radius;
0666     u1[Y] = v1[Y] / radius;
0667     u1[Z] = v1[Z] / radius;
0668 
0669     radius = sqrt (v2[X]*v2[X] + v2[Y]*v2[Y] + v2[Z]*v2[Z]);
0670     u2[X] = v2[X] / radius;
0671     u2[Y] = v2[Y] / radius;
0672     u2[Z] = v2[Z] / radius;
0673 
0674     // The vector is reversed for anti-clockwise rotations.
0675     // The angle is positive, for both clockwise and anti-clockwise.
0676     double rad  = acos (u1[X]*u2[X] + u1[Y]*u2[Y] + u1[Z]*u2[Z]);
0677     if (fabs (rad) >=  0.0001) {
0678     // The angle is of reasonable size.  It is safe to do the divisions.
0679     double srad = sin (rad);
0680     turnAxis[X] = -(u1[Y]*u2[Z] - u1[Z]*u2[Y]) / srad;
0681     turnAxis[Y] = -(u1[Z]*u2[X] - u1[X]*u2[Z]) / srad;
0682     turnAxis[Z] = -(u1[X]*u2[Y] - u1[Y]*u2[X]) / srad;
0683     result = true;          // Calculation succeeded.
0684     }
0685     else {
0686     // The angle is less than 1 minute of arc. Avoid overflow on division.
0687     rad = 0.0;          // Zero angle (no rotation).
0688     turnAxis[X] = 1.0;      // Arbitrary axis (does not matter).
0689     turnAxis[Y] = 0.0;
0690     turnAxis[Z] = 0.0;
0691     result = false;         // Calculation failed.
0692     }
0693     turnAngle = rad * 180.0 / M_PI;
0694     radius = sqrt (turnAxis[X]*turnAxis[X] +
0695         turnAxis[Y]*turnAxis[Y] + turnAxis[Z]*turnAxis[Z]);
0696     return result;
0697 }
0698 
0699 
0700 GLfloat MoveTracker::getMousePosition (const int mX, const int mY, double pos[])
0701 {
0702     const qreal dpr = myParent->devicePixelRatio();
0703 
0704     GLfloat depth;
0705 
0706     // Read the depth value at the position on the screen.
0707     glReadPixels (mX * dpr, mY * dpr, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
0708 
0709     // Get the mouse position in OpenGL world co-ordinates.
0710     getAbsGLPosition (mX, mY, depth, pos);
0711 
0712     return depth;
0713 }
0714 
0715 
0716 void MoveTracker::getAbsGLPosition (int sX, int sY, GLfloat depth, double pos[])
0717 {
0718     getGLPosition (sX, sY, depth, modelViewMatrix, pos);
0719 }
0720 
0721 
0722 void MoveTracker::getGLPosition (int sX, int sY, GLfloat depth,
0723                     double matrix[], double pos[])
0724 {
0725     const qreal dpr = myParent->devicePixelRatio();
0726 
0727     // Find the world coordinates of the nearest object at the screen position.
0728     GLdouble objx, objy, objz;
0729     GLint ret = gluUnProject (sX * dpr, sY * dpr, depth,
0730                   matrix, projectionMatrix, viewPort,
0731                   &objx, &objy, &objz);
0732 
0733     if (ret != GL_TRUE) {
0734     qCDebug(KUBRICK_LOG) << "gluUnProject() unsuccessful at point" << sX << sY << depth;
0735     return;
0736     }
0737 
0738     // Return the OpenGL coordinates we found.
0739     pos[X] = objx; pos[Y] = objy; pos[Z] = objz;
0740 }
0741 
0742 
0743 void MoveTracker::usersRotation()
0744 {
0745     glMultMatrixf (rotationMatrix);
0746 }
0747 
0748 #include "moc_movetracker.cpp"