File indexing completed on 2024-04-28 07:54:39

0001 /*
0002     SPDX-FileCopyrightText: 2008 Ian Wadham <iandw.au@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own header
0008 #include "cube.h"
0009 
0010 // The RubikCube object uses the sqrt() function.
0011 #include <cmath>
0012 #include <cstdlib>
0013 
0014 // Local includes
0015 #include "gameglview.h"
0016 
0017 // Create a Cube
0018 
0019 Cube::Cube (QObject * parent, int xlen, int ylen, int zlen)
0020     : QObject (parent)
0021 {
0022     sizes [X] = xlen;
0023     sizes [Y] = ylen;
0024     sizes [Z] = zlen;
0025 
0026     // Generate a list of all the cubies in the cube.
0027     // Set the centres of cubies at +ve and -ve co-ords around (0,0,0),
0028     // as documented in "game.h".  The cubie dimensions are 2x2x2.  Each
0029     // cubie's centre co-ordinate is (2*index - end-face-pos + 1).
0030     int centre [nAxes];
0031 
0032     qDeleteAll(cubies);
0033     cubies.clear();
0034     LOOP (i, sizes [X]) {
0035     centre [X] = 2*i - sizes [X] + 1;
0036     LOOP (j, sizes [Y]) {
0037         centre [Y] = 2*j - sizes [Y] + 1;
0038         LOOP (k, sizes [Z]) {
0039         centre [Z] = 2*k - sizes [Z] + 1;
0040         cubies.append (new Cubie (centre));
0041         }
0042     }
0043     }
0044 
0045     addStickers ();     // Add colored stickers to the faces.
0046 
0047     setBlinkingOff ();
0048     moveInProgressAxis   = Z;   // Front face (+Z).
0049     moveInProgressSlice  = sizes[Z] - 1;
0050     moveInProgressAngle  = 0;
0051 }
0052 
0053 Cube::~Cube ()
0054 {
0055     qDeleteAll(cubies);
0056 }
0057 
0058 void Cube::moveSlice (Axis axis, int location, Rotation direction)
0059 {
0060     // If single-slice and not square, rotate 180 degrees rather than 90.
0061     if ((location != WHOLE_CUBE) &&
0062     (sizes [(axis + 1)%nAxes] != sizes [(axis + 2)%nAxes])) {
0063     direction = ONE_EIGHTY;
0064     }
0065 
0066     // Rotate all cubies that are in the required slice.
0067     for (Cubie * cubie : std::as_const(cubies)) {
0068     cubie->rotate (axis, location, direction);
0069     }
0070     setBlinkingOff ();
0071 }
0072 
0073 void Cube::addStickers ()
0074 {
0075     int color = INTERNAL;           // ie. Zero.
0076 
0077     // Add stickers to cube faces in the order of axes X/Y/Z then -ve/+ve end.
0078     LOOP (n, nAxes) {
0079     LOOP (minusPlus, 2) {
0080         int sign = 2*minusPlus - 1;     // sign = -1 or +1.
0081         int location = sign * sizes [n];
0082 
0083         color++;                // FaceColor enum 1 --> 6.
0084         for (Cubie * cubie : std::as_const(cubies)) {
0085         cubie->addSticker ((FaceColor) color, (Axis) n, location, sign);
0086         }
0087     }
0088     }
0089 }
0090 
0091 
0092 void Cube::drawCube (GameGLView * gameGLView, float cubieSize)
0093 {
0094     // For each cubie in the cube ...
0095     for (Cubie * cubie : std::as_const(cubies)) {
0096 
0097     if (cubie->hasNoStickers()) {
0098         // This cubie is deep inside the cube: save time by not drawing it.
0099         continue;
0100     }
0101 
0102     // Draw the cubie and its stickers.
0103     cubie->drawCubie (gameGLView, cubieSize,
0104           moveInProgressAxis, moveInProgressSlice, moveInProgressAngle);
0105     }
0106 }
0107 
0108 
0109 bool Cube::findSticker (double position [], float myCubieSize,
0110                 int faceCentre [])
0111 {
0112     bool             result       = false;
0113     double           location [nAxes];
0114     double           distance     = sqrt ((double) 2.0);
0115 
0116     // Calculate the position in the cube's internal co-ordinate system.
0117     LOOP (i, nAxes) {
0118         location [i] = (position [i] / myCubieSize) * 2.0;
0119         // IDW faceCentre [i] = 0;      // Return zeroes if no sticker is found.
0120     }
0121 
0122     for (Cubie * cubie : std::as_const(cubies)) {
0123         double d = cubie->findCloserSticker (distance, location, faceCentre);
0124         if (d < distance) {
0125             distance = d;
0126             result = true;
0127         }
0128     }
0129 
0130     return (result);
0131 }
0132 
0133 
0134 void Cube::setMoveInProgress (Axis axis, int location)
0135 {
0136     setBlinkingOff ();
0137     moveInProgressAxis   = axis;
0138     moveInProgressSlice  = location;
0139 }
0140 
0141 
0142 void Cube::setMoveAngle (int angle)
0143 {
0144     moveInProgressAngle  = angle;
0145 }
0146 
0147 
0148 void Cube::setBlinkingOn (Axis axis, int location)
0149 {
0150     for (Cubie * cubie : std::as_const(cubies)) {
0151     cubie->setBlinkingOn (axis, location, sizes[axis]);
0152     }
0153 }
0154 
0155 
0156 void Cube::setBlinkingOff ()
0157 {
0158     for (Cubie * cubie : std::as_const(cubies)) {
0159     cubie->setBlinkingOff ();
0160     }
0161 }
0162 
0163 
0164 int Cube::faceNormal (int faceCentre [3])
0165 {
0166     LOOP (i, nAxes) {
0167     if (abs(faceCentre [i]) == sizes [i]) {
0168         return i;
0169     }
0170     }
0171     return 0;
0172 }
0173 
0174 
0175 double Cube::convToOpenGL (int internalCoord, double cubieSize)
0176 {
0177     return ((double) internalCoord / 2.0) * cubieSize;
0178 }
0179 
0180 Cubie::Cubie (int centre [nAxes])
0181 {
0182     LOOP (i, nAxes) {
0183     originalCentre [i] = centre [i];
0184     currentCentre  [i] = centre [i];
0185     }
0186 }
0187 
0188 
0189 Cubie::~Cubie ()
0190 {
0191     qDeleteAll(stickers);
0192 }
0193 
0194 
0195 void Cubie::rotate (Axis axis, int location, Rotation direction)
0196 {
0197     // Cubie moves only if it is in the required slice or in a whole-cube move.
0198     if ((location != WHOLE_CUBE) && (currentCentre [axis] != location)) {
0199     return;
0200     }
0201 
0202     // The co-ordinate on the axis of rotation does not change, but we must
0203     // work out what the other two co-ordinates are, in cyclical order: i.e.
0204     //         X-axis (0) --> Y,Z (co-ordinates 1 and 2 change),
0205     //         Y-axis (1) --> Z,X (co-ordinates 2 and 0 change),
0206     //         Z-axis (2) --> X,Y (co-ordinates 0 and 1 change). 
0207     //
0208     Axis coord1 = (Axis) ((axis + 1) % nAxes);
0209     Axis coord2 = (Axis) ((axis + 2) % nAxes);
0210     int  temp;
0211 
0212     switch (direction) {
0213     case (ANTICLOCKWISE):   // eg. around the Z-axis, X --> Y and Y --> -X.
0214         temp   = currentCentre [coord1];
0215         currentCentre [coord1] = - currentCentre [coord2];
0216         currentCentre [coord2] = + temp;
0217         for (Sticker * s : std::as_const(stickers)) {
0218         temp   = s->currentFaceCentre [coord1];
0219         s->currentFaceCentre [coord1] = - s->currentFaceCentre [coord2];
0220         s->currentFaceCentre [coord2] = + temp;
0221         }
0222         break;
0223     case (CLOCKWISE):   // eg. around the Z-axis, X --> -Y and Y --> X.
0224         temp   = currentCentre [coord1];
0225         currentCentre [coord1] = + currentCentre [coord2];
0226         currentCentre [coord2] = - temp;
0227         for (Sticker * s : std::as_const(stickers)) {
0228         temp   = s->currentFaceCentre [coord1];
0229         s->currentFaceCentre [coord1] = + s->currentFaceCentre [coord2];
0230         s->currentFaceCentre [coord2] = - temp;
0231         }
0232         break;
0233     case (ONE_EIGHTY):  // eg. around the Z-axis, X --> -X and Y --> -Y.
0234         currentCentre [coord1] = - currentCentre [coord1];
0235         currentCentre [coord2] = - currentCentre [coord2];
0236         for (Sticker * s : std::as_const(stickers)) {
0237         s->currentFaceCentre [coord1] = - s->currentFaceCentre [coord1];
0238         s->currentFaceCentre [coord2] = - s->currentFaceCentre [coord2];
0239         }
0240         break;
0241     default:
0242         break;
0243     }
0244 }
0245 
0246 
0247 void Cubie::addSticker (FaceColor color, Axis axis, int location, int sign)
0248 {
0249     // The cubie will get a sticker only if it is on the required face.
0250     if (originalCentre [axis] != (location - sign)) {
0251     return;
0252     }
0253 
0254     // Create a sticker.
0255     Sticker * s = new Sticker;
0256     s->color    = color;
0257     s->blinking = false;
0258     LOOP (n, nAxes) {
0259     // The co-ordinates not on "axis" are the same as at the cubie's centre.
0260     s->originalFaceCentre [n] = originalCentre [n];
0261     s->currentFaceCentre  [n] = originalCentre [n];
0262     }
0263 
0264     // The co-ordinate on "axis" is offset by -1 or +1 from the cubie's centre.
0265     s->originalFaceCentre [axis] = location;
0266     s->currentFaceCentre  [axis] = location;
0267 
0268     // Put the sticker on the cubie.
0269     stickers.append (s);
0270 }
0271 
0272 
0273 bool Cubie::hasNoStickers ()
0274 {
0275     return (stickers.isEmpty ());
0276 }
0277 
0278 
0279 void Cubie::drawCubie (GameGLView * gameGLView, float cubieSize,
0280                 Axis axis, int slice, int angle)
0281 {
0282     float centre     [nAxes];
0283 
0284     // Calculate the centre of the cubie in OpenGL co-ordinates.
0285     LOOP (i, nAxes) {
0286     centre [i] = ((float) currentCentre [i]) * cubieSize / 2.0;
0287     }
0288 
0289     // If this cubie is in a moving slice, set its animation angle.
0290     int   myAngle = 0;
0291     if ((angle != 0) && ((slice == WHOLE_CUBE) ||
0292              (currentCentre [axis] == slice))) {
0293     myAngle = angle;
0294     }
0295 
0296     // Draw this cubie in color zero (grey plastic color).
0297     gameGLView->drawACubie (cubieSize, centre, axis, myAngle);
0298 
0299     float faceCentre [nAxes];
0300     int   faceNormal [nAxes];
0301 
0302     // For each sticker on this cubie (there may be 0->3 stickers) ...
0303     for (Sticker * sticker : std::as_const(stickers)) {
0304     // Calculate the integer unit-vector normal to this sticker's face
0305     // and the centre of the face, in floating OpenGL co-ordinates.
0306     LOOP (j, nAxes) {
0307         faceNormal [j] = sticker->currentFaceCentre [j] - currentCentre [j];
0308         faceCentre [j] = ((float) sticker->currentFaceCentre [j]) *
0309                         cubieSize / 2.0;
0310     }
0311 
0312     // Draw this sticker in the required color, blink-intensity and size.
0313     gameGLView->drawASticker (cubieSize, (int) sticker->color,
0314               sticker->blinking, faceNormal, faceCentre);
0315     }
0316 
0317     // If cubie is moving, re-align the OpenGL axes with the rest of the cube.
0318     if (myAngle != 0) {
0319     gameGLView->finishCubie ();
0320     }
0321 }
0322 
0323 
0324 double Cubie::findCloserSticker (double distance, double location [],
0325                  int faceCentre [])
0326 {
0327     double    len          = 0.0;
0328     double    dmin         = distance;
0329     Sticker * foundSticker = nullptr;
0330 
0331     for (Sticker * sticker : std::as_const(stickers)) {
0332     double d = 0.0;
0333     LOOP (n, nAxes) {
0334         len = location[n] - sticker->currentFaceCentre[n];
0335         d   = d + len * len;
0336     }
0337     d = sqrt (d);
0338     if (d < dmin) {
0339         dmin = d;
0340         foundSticker = sticker;
0341     }
0342     }
0343 
0344     if (foundSticker != nullptr) {
0345     LOOP (n, nAxes) {
0346         faceCentre[n] = foundSticker->currentFaceCentre[n];
0347     }
0348     }
0349 
0350     return (dmin);
0351 }
0352 
0353 
0354 void Cubie::setBlinkingOn (Axis axis, int location, int cubeBoundary)
0355 {
0356     // Exit if the cubie is not in the slice that is going to move.
0357     if ((location != WHOLE_CUBE) && (currentCentre [axis] != location)) {
0358     return;
0359     }
0360 
0361     // If the sticker is on the outside edges of the slice, make it blink, but
0362     // not if it is perpendicular to the move-axis (ie. on the slice's face).
0363     for (Sticker * sticker : std::as_const(stickers)) {
0364     if (abs(sticker->currentFaceCentre [axis]) != cubeBoundary) {
0365         sticker->blinking = true;
0366     }
0367     }
0368 }
0369 
0370 
0371 void Cubie::setBlinkingOff ()
0372 {
0373     for (Sticker * sticker : std::as_const(stickers)) {
0374     sticker->blinking = false;
0375     }
0376 }
0377 
0378 
0379 void Cubie::printAll ()
0380 {
0381     printf ("%2d %2d %2d -> %2d %2d %2d Stickers: ",
0382         originalCentre[X], originalCentre[Y], originalCentre[Z],
0383         currentCentre[X],  currentCentre[Y],  currentCentre[Z]);
0384 
0385     if (stickers.isEmpty ()) {
0386     printf ("<NONE>\n");
0387     }
0388     else {
0389     for (Sticker * sticker : std::as_const(stickers)) {
0390         printf ("<%d> at ", (int) sticker->color);
0391         LOOP (n, nAxes) {
0392         printf ("%2d ", sticker->currentFaceCentre [n]);
0393         }
0394     }
0395     printf ("\n");
0396     }
0397 }
0398 
0399 
0400 void Cubie::printChanges ()
0401 {
0402     bool moved = false;
0403 
0404     // Check if the cubie's centre is in a new position.
0405     LOOP (i, nAxes) {
0406     if (currentCentre  [i] != originalCentre [i])
0407         moved = true;
0408     }
0409 
0410     // Check if the cubie is back where it was but has been given a twist.
0411     if (! moved) {
0412     for (Sticker * s : std::as_const(stickers)) {
0413         LOOP (i, nAxes) {
0414         if (s->currentFaceCentre [i] != s->originalFaceCentre [i])
0415             moved = true;
0416         }
0417     }
0418     }
0419 
0420     // If anything has changed, print the cubie.
0421     if (moved) {
0422     printAll ();
0423     }
0424 }
0425 
0426