File indexing completed on 2024-03-24 04:07:46
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