Project

General

Profile

Download (17.5 KB) Statistics
| Branch: | Tag: | Revision:

magiccube / src / main / java / org / distorted / objects / TwistyCube.java @ a64e07d0

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2019 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is free software: you can redistribute it and/or modify                            //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Magic Cube is distributed in the hope that it will be useful,                                 //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.objects;
21

    
22
import android.content.res.Resources;
23
import android.graphics.Canvas;
24
import android.graphics.Paint;
25

    
26
import org.distorted.library.main.DistortedEffects;
27
import org.distorted.library.main.DistortedTexture;
28
import org.distorted.library.mesh.MeshBase;
29
import org.distorted.library.mesh.MeshSquare;
30
import org.distorted.library.type.Static3D;
31
import org.distorted.library.type.Static4D;
32
import org.distorted.main.R;
33
import org.distorted.main.RubikSurfaceView;
34

    
35
import java.util.Random;
36

    
37
import static org.distorted.effects.scramble.ScrambleEffect.START_AXIS;
38

    
39
///////////////////////////////////////////////////////////////////////////////////////////////////
40

    
41
class TwistyCube extends TwistyObject
42
{
43
  // the three rotation axis of a RubikCube. Must be normalized.
44
  static final Static3D[] ROT_AXIS = new Static3D[]
45
         {
46
           new Static3D(1,0,0),
47
           new Static3D(0,1,0),
48
           new Static3D(0,0,1)
49
         };
50

    
51
  private static final int[] FACE_COLORS = new int[]
52
         {
53
           COLOR_YELLOW, COLOR_WHITE,
54
           COLOR_BLUE  , COLOR_GREEN,
55
           COLOR_RED   , COLOR_ORANGE
56
         };
57

    
58
  // All legal rotation quats of a RubikCube of any size.
59
  // Here's how to compute this:
60
  // 1) compute how many rotations there are (RubikCube of any size = 24)
61
  // 2) take the AXIS, angles of rotation (90 in RubikCube's case) compute the basic quaternions
62
  // (i.e. rotations of 1 basic angle along each of the axis) and from there start semi-randomly
63
  // multiplying them and eventually you'll find all (24) legal rotations.
64
  // Example program in C, res/raw/compute_quats.c , is included.
65
  private static final Static4D[] QUATS = new Static4D[]
66
         {
67
         new Static4D(  0.0f,   0.0f,   0.0f,   1.0f),
68
         new Static4D(  1.0f,   0.0f,   0.0f,   0.0f),
69
         new Static4D(  0.0f,   1.0f,   0.0f,   0.0f),
70
         new Static4D(  0.0f,   0.0f,   1.0f,   0.0f),
71

    
72
         new Static4D( SQ2/2,  SQ2/2,  0.0f ,   0.0f),
73
         new Static4D( SQ2/2, -SQ2/2,  0.0f ,   0.0f),
74
         new Static4D( SQ2/2,   0.0f,  SQ2/2,   0.0f),
75
         new Static4D(-SQ2/2,   0.0f,  SQ2/2,   0.0f),
76
         new Static4D( SQ2/2,   0.0f,   0.0f,  SQ2/2),
77
         new Static4D( SQ2/2,   0.0f,   0.0f, -SQ2/2),
78
         new Static4D(  0.0f,  SQ2/2,  SQ2/2,   0.0f),
79
         new Static4D(  0.0f,  SQ2/2, -SQ2/2,   0.0f),
80
         new Static4D(  0.0f,  SQ2/2,   0.0f,  SQ2/2),
81
         new Static4D(  0.0f,  SQ2/2,   0.0f, -SQ2/2),
82
         new Static4D(  0.0f,   0.0f,  SQ2/2,  SQ2/2),
83
         new Static4D(  0.0f,   0.0f,  SQ2/2, -SQ2/2),
84

    
85
         new Static4D(  0.5f,   0.5f,   0.5f,   0.5f),
86
         new Static4D(  0.5f,   0.5f,  -0.5f,   0.5f),
87
         new Static4D(  0.5f,   0.5f,  -0.5f,  -0.5f),
88
         new Static4D(  0.5f,  -0.5f,   0.5f,  -0.5f),
89
         new Static4D( -0.5f,  -0.5f,  -0.5f,   0.5f),
90
         new Static4D( -0.5f,   0.5f,  -0.5f,  -0.5f),
91
         new Static4D( -0.5f,   0.5f,   0.5f,  -0.5f),
92
         new Static4D( -0.5f,   0.5f,   0.5f,   0.5f)
93
         };
94

    
95
  private static MeshBase[] mMeshes;
96

    
97
///////////////////////////////////////////////////////////////////////////////////////////////////
98

    
99
  TwistyCube(int size, Static4D quat, DistortedTexture texture,
100
             MeshSquare mesh, DistortedEffects effects, int[][] moves, Resources res, int scrWidth)
101
    {
102
    super(size, size, 60, quat, texture, mesh, effects, moves, ObjectList.CUBE, res, scrWidth);
103
    }
104

    
105
///////////////////////////////////////////////////////////////////////////////////////////////////
106

    
107
  MeshBase createCubitMesh(int cubit, int numLayers)
108
    {
109
    if( mMeshes==null )
110
      {
111
      mMeshes = new MeshBase[ObjectList.CUBE.getNumVariants()];
112
      }
113

    
114
    int ordinal= ObjectList.CUBE.ordinal();
115
    int index  = ObjectList.getSizeIndex(ordinal,getNumLayers());
116

    
117
    if( mMeshes[index]==null )
118
      {
119
      mMeshes[index] = FactoryCubit.getInstance().createCubeMesh(index);
120
      }
121

    
122
    return mMeshes[index].copy(true);
123
    }
124

    
125
///////////////////////////////////////////////////////////////////////////////////////////////////
126

    
127
  void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top)
128
    {
129
    float F =  0.5f;
130
    float R = 0.10f;
131
    float S = 0.08f;
132
    float[] vertices = { -F,-F, +F,-F, +F,+F, -F,+F};
133

    
134
    FactorySticker factory = FactorySticker.getInstance();
135
    factory.drawRoundedPolygon(canvas, paint, left, top, vertices, S, FACE_COLORS[face], R);
136
    }
137

    
138
///////////////////////////////////////////////////////////////////////////////////////////////////
139

    
140
  Static3D[] getCubitPositions(int size)
141
    {
142
    int numCubits = size>1 ? 6*size*size - 12*size + 8 : 1;
143
    Static3D[] tmp = new Static3D[numCubits];
144

    
145
    float diff = 0.5f*(size-1);
146
    int currentPosition = 0;
147

    
148
    for(int x = 0; x<size; x++)
149
      for(int y = 0; y<size; y++)
150
        for(int z = 0; z<size; z++)
151
          if( x==0 || x==size-1 || y==0 || y==size-1 || z==0 || z==size-1 )
152
            {
153
            tmp[currentPosition++] = new Static3D(x-diff,y-diff,z-diff);
154
            }
155

    
156
    return tmp;
157
    }
158

    
159
///////////////////////////////////////////////////////////////////////////////////////////////////
160

    
161
  Static4D[] getQuats()
162
    {
163
    return QUATS;
164
    }
165

    
166
///////////////////////////////////////////////////////////////////////////////////////////////////
167

    
168
  boolean shouldResetTextureMaps()
169
    {
170
    return false;
171
    }
172

    
173
///////////////////////////////////////////////////////////////////////////////////////////////////
174

    
175
  int getNumFaces()
176
    {
177
    return FACE_COLORS.length;
178
    }
179

    
180
///////////////////////////////////////////////////////////////////////////////////////////////////
181

    
182
  float[] getCuts(int numLayers)
183
    {
184
    float[] cuts = new float[numLayers-1];
185

    
186
    for(int i=0; i<numLayers-1; i++)
187
      {
188
      cuts[i] = (2-numLayers)*0.5f + i;
189
      }
190

    
191
    return cuts;
192
    }
193

    
194
///////////////////////////////////////////////////////////////////////////////////////////////////
195

    
196
  int getNumStickerTypes(int numLayers)
197
    {
198
    return 1;
199
    }
200

    
201
///////////////////////////////////////////////////////////////////////////////////////////////////
202

    
203
  int getNumCubitFaces()
204
    {
205
    return FACE_COLORS.length;
206
    }
207

    
208
///////////////////////////////////////////////////////////////////////////////////////////////////
209

    
210
  float getScreenRatio()
211
    {
212
    return 0.5f;
213
    }
214

    
215
///////////////////////////////////////////////////////////////////////////////////////////////////
216

    
217
  int getFaceColor(int cubit, int cubitface, int size)
218
    {
219
    float diff = CUBITS[cubit].mRotationRow[cubitface/2] - (cubitface%2==0 ? size-1:0);
220
    return diff*diff < 0.0001f ? cubitface : NUM_FACES;
221
    }
222

    
223
///////////////////////////////////////////////////////////////////////////////////////////////////
224

    
225
  float returnMultiplier()
226
    {
227
    return getNumLayers();
228
    }
229

    
230
///////////////////////////////////////////////////////////////////////////////////////////////////
231

    
232
  float[] getRowChances(int numLayers)
233
    {
234
    float[] chances = new float[numLayers];
235

    
236
    for(int i=0; i<numLayers; i++)
237
      {
238
      chances[i] = (i+1.0f) / numLayers;
239
      }
240

    
241
    return chances;
242
    }
243

    
244
///////////////////////////////////////////////////////////////////////////////////////////////////
245
// PUBLIC API
246

    
247
  public Static3D[] getRotationAxis()
248
    {
249
    return ROT_AXIS;
250
    }
251

    
252
///////////////////////////////////////////////////////////////////////////////////////////////////
253

    
254
  public int getBasicAngle()
255
    {
256
    return 4;
257
    }
258

    
259
///////////////////////////////////////////////////////////////////////////////////////////////////
260

    
261
  public int randomizeNewRotAxis(Random rnd, int oldRotAxis)
262
    {
263
    int numAxis = ROTATION_AXIS.length;
264

    
265
    if( oldRotAxis == START_AXIS )
266
      {
267
      return rnd.nextInt(numAxis);
268
      }
269
    else
270
      {
271
      int newVector = rnd.nextInt(numAxis-1);
272
      return (newVector>=oldRotAxis ? newVector+1 : newVector);
273
      }
274
    }
275

    
276
///////////////////////////////////////////////////////////////////////////////////////////////////
277

    
278
  public int randomizeNewRow(Random rnd, int oldRotAxis, int oldRow, int newRotAxis)
279
    {
280
    float rowFloat = rnd.nextFloat();
281

    
282
    for(int row=0; row<mRowChances.length; row++)
283
      {
284
      if( rowFloat<=mRowChances[row] ) return row;
285
      }
286

    
287
    return 0;
288
    }
289

    
290
///////////////////////////////////////////////////////////////////////////////////////////////////
291

    
292
  public boolean isSolved()
293
    {
294
    int index = CUBITS[0].mQuatIndex;
295

    
296
    for(int i=1; i<NUM_CUBITS; i++)
297
      {
298
      if( !thereIsNoVisibleDifference(CUBITS[i], index) ) return false;
299
      }
300

    
301
    return true;
302
    }
303

    
304
///////////////////////////////////////////////////////////////////////////////////////////////////
305
// return if the Cubit, when rotated with its own mQuatScramble, would have looked any different
306
// then if it were rotated by quaternion 'quat'.
307
// No it is not so simple as the quats need to be the same - imagine a 4x4x4 cube where the two
308
// middle squares get interchanged. No visible difference!
309
//
310
// So: this is true iff the cubit
311
// a) is a corner or edge and the quaternions are the same
312
// b) is inside one of the faces and after rotations by both quats it ends up on the same face.
313

    
314
  private boolean thereIsNoVisibleDifference(Cubit cubit, int quatIndex)
315
    {
316
    if ( cubit.mQuatIndex == quatIndex ) return true;
317

    
318
    int belongsToHowManyFaces = 0;
319
    int lastLayer = getNumLayers()-1;
320
    float row;
321
    final float MAX_ERROR = 0.01f;
322

    
323
    for(int i=0; i<NUM_AXIS; i++)
324
      {
325
      row = cubit.mRotationRow[i];
326
      if( (row          <MAX_ERROR && row          >-MAX_ERROR) ||
327
          (row-lastLayer<MAX_ERROR && row-lastLayer>-MAX_ERROR)  ) belongsToHowManyFaces++;
328
      }
329

    
330
    switch(belongsToHowManyFaces)
331
      {
332
      case 0 : return true ;  // 'inside' cubit that does not lie on any face
333
      case 1 :                // cubit that lies inside one of the faces
334
               Static3D orig = cubit.getOrigPosition();
335
               Static4D quat1 = QUATS[quatIndex];
336
               Static4D quat2 = QUATS[cubit.mQuatIndex];
337

    
338
               Static4D cubitCenter = new Static4D( orig.get0(), orig.get1(), orig.get2(), 0);
339
               Static4D rotated1 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat1 );
340
               Static4D rotated2 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat2 );
341

    
342
               float row1, row2;
343
               float x1 = rotated1.get0();
344
               float y1 = rotated1.get1();
345
               float z1 = rotated1.get2();
346
               float x2 = rotated2.get0();
347
               float y2 = rotated2.get1();
348
               float z2 = rotated2.get2();
349

    
350
               for(int i=0; i<NUM_AXIS; i++)
351
                 {
352
                 row1 = computeRow(x1,y1,z1,i);
353
                 row2 = computeRow(x2,y2,z2,i);
354

    
355
                 if( (row1==0 && row2==0) || (row1==lastLayer && row2==lastLayer) ) return true;
356
                 }
357
               return false;
358

    
359
      default: return false;  // edge or corner
360
      }
361
    }
362

    
363
///////////////////////////////////////////////////////////////////////////////////////////////////
364
// order: Up --> Right --> Front --> Down --> Left --> Back
365
// (because the first implemented Solver - the two-phase Cube3 one - expects such order)
366
//
367
// Solved 3x3x3 Cube maps to "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"
368
//
369
// s : size of the cube; let index = a*s + b    (i.e. a,b = row,column)
370
//
371
// Up    :   index --> b<s-1 ? (s-1)*(s+4b)+a : 6*s*s -13*s +8 +a
372
// Right :   index --> 6*s*s - 12*s + 7 - index
373
// Front :   index --> if b==0  : s*s - 1 - index
374
//                     if b==s-1: 6*s*s -11*s +6 - index
375
//                     else
376
//                         a==0: s*s + s-1 + 4*(b-1)*(s-1) + 2*(s-2) + s
377
//                         else: s*s + s-1 + 4*(b-1)*(s-1) + 2*(s-1-a)
378
// Down  :   index --> b==0 ? (s-1-a) : s*s + s-1 + 4*(b-1)*(s-1) - a
379
// Left  :   index --> (s-1-a)*s + b
380
// Back  :   index --> if b==s-1: s*(s-1-a)
381
//                     if b==0  : 5*s*s -12*s + 8 + (s-1-a)*s
382
//                     else
383
//                        if a==s-1: s*s + 4*(s-2-b)*(s-1)
384
//                        else     : s*s + 4*(s-2-b)*(s-1) + s + (s-2-a)*2
385

    
386
  public String retObjectString()
387
    {
388
    StringBuilder objectString = new StringBuilder();
389
    int layers = getNumLayers();
390
    int len = layers*layers;
391
    int cubitIndex, row, col, color,face;
392

    
393
    final int RIGHT= 0;
394
    final int LEFT = 1;
395
    final int UP   = 2;
396
    final int DOWN = 3;
397
    final int FRONT= 4;
398
    final int BACK = 5;
399

    
400
    // 'I' - interior, theoretically can happen
401
    final char[] FACE_NAMES = { 'R', 'L', 'U', 'D', 'F', 'B', 'I'};
402

    
403
    face = UP;
404

    
405
    for(int i=0; i<len; i++)
406
      {
407
      row = i/layers;
408
      col = i%layers;
409

    
410
      cubitIndex = col<layers-1 ? (layers-1)*(layers+4*col) + row : 6*layers*layers - 13*layers + 8 + row;
411
      color = getCubitFaceColorIndex(cubitIndex,face);
412
      objectString.append(FACE_NAMES[color]);
413
      }
414

    
415
    face = RIGHT;
416

    
417
    for(int i=0; i<len; i++)
418
      {
419
      cubitIndex = 6*layers*layers - 12*layers +7 - i;
420
      color = getCubitFaceColorIndex(cubitIndex,face);
421
      objectString.append(FACE_NAMES[color]);
422
      }
423

    
424
    face = FRONT;
425

    
426
    for(int i=0; i<len; i++)
427
      {
428
      row = i/layers;
429
      col = i%layers;
430

    
431
      if( col==layers-1 ) cubitIndex = 6*layers*layers - 11*layers + 6 -i;
432
      else if(   col==0 ) cubitIndex = layers*layers - 1 - i;
433
      else
434
        {
435
        if( row==0 ) cubitIndex = layers*layers + layers-1 + 4*(col-1)*(layers-1) + 2*(layers-2) + layers;
436
        else         cubitIndex = layers*layers + layers-1 + 4*(col-1)*(layers-1) + 2*(layers-1-row);
437
        }
438

    
439
      color = getCubitFaceColorIndex(cubitIndex,face);
440
      objectString.append(FACE_NAMES[color]);
441
      }
442

    
443
    face = DOWN;
444

    
445
    for(int i=0; i<len; i++)
446
      {
447
      row = i/layers;
448
      col = i%layers;
449

    
450
      cubitIndex = col==0 ? layers-1-row : layers*layers + layers-1 + 4*(col-1)*(layers-1) - row;
451
      color = getCubitFaceColorIndex(cubitIndex,face);
452
      objectString.append(FACE_NAMES[color]);
453
      }
454

    
455
    face = LEFT;
456

    
457
    for(int i=0; i<len; i++)
458
      {
459
      row = i/layers;
460
      col = i%layers;
461

    
462
      cubitIndex = (layers-1-row)*layers + col;
463
      color = getCubitFaceColorIndex(cubitIndex,face);
464
      objectString.append(FACE_NAMES[color]);
465
      }
466

    
467
    face = BACK;
468

    
469
    for(int i=0; i<len; i++)
470
      {
471
      row = i/layers;
472
      col = i%layers;
473

    
474
      if( col==layers-1 ) cubitIndex = layers*(layers-1-row);
475
      else if(   col==0 ) cubitIndex = 5*layers*layers - 12*layers + 8 + (layers-1-row)*layers;
476
      else
477
        {
478
        if( row==layers-1 ) cubitIndex = layers*layers + 4*(layers-2-col)*(layers-1);
479
        else                cubitIndex = layers*layers + 4*(layers-2-col)*(layers-1) + layers + 2*(layers-2-row);
480
        }
481

    
482
      color = getCubitFaceColorIndex(cubitIndex,face);
483
      objectString.append(FACE_NAMES[color]);
484
      }
485

    
486
    return objectString.toString();
487
    }
488

    
489
///////////////////////////////////////////////////////////////////////////////////////////////////
490

    
491
  public int getObjectName(int numLayers)
492
    {
493
    switch(numLayers)
494
      {
495
      case 2: return R.string.cube2;
496
      case 3: return R.string.cube3;
497
      case 4: return R.string.cube4;
498
      case 5: return R.string.cube5;
499
      }
500
    return R.string.cube3;
501
    }
502

    
503
///////////////////////////////////////////////////////////////////////////////////////////////////
504

    
505
  public int getInventor(int numLayers)
506
    {
507
    switch(numLayers)
508
      {
509
      case 2: return R.string.cube2_inventor;
510
      case 3: return R.string.cube3_inventor;
511
      case 4: return R.string.cube4_inventor;
512
      case 5: return R.string.cube5_inventor;
513
      }
514
    return R.string.cube3_inventor;
515
    }
516

    
517
///////////////////////////////////////////////////////////////////////////////////////////////////
518

    
519
  public int getComplexity(int numLayers)
520
    {
521
    switch(numLayers)
522
      {
523
      case 2: return 4;
524
      case 3: return 6;
525
      case 4: return 8;
526
      case 5: return 10;
527
      }
528
    return 6;
529
    }
530
}
(16-16/30)