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"