Project

General

Profile

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

magiccube / src / main / java / org / distorted / objects / TwistyCube.java @ 323b217c

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)
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 size)
183
    {
184
    float[] cuts = new float[size-1];
185

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

    
191
    return cuts;
192
    }
193

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

    
196
  int getNumStickerTypes()
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()
233
    {
234
    int numLayers = getNumLayers();
235
    float[] chances = new float[numLayers];
236

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

    
242
    return chances;
243
    }
244

    
245
///////////////////////////////////////////////////////////////////////////////////////////////////
246
// PUBLIC API
247

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

    
253
///////////////////////////////////////////////////////////////////////////////////////////////////
254

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

    
260
///////////////////////////////////////////////////////////////////////////////////////////////////
261

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

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

    
277
///////////////////////////////////////////////////////////////////////////////////////////////////
278

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

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

    
288
    return 0;
289
    }
290

    
291
///////////////////////////////////////////////////////////////////////////////////////////////////
292

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

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

    
302
    return true;
303
    }
304

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
404
    face = UP;
405

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

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

    
416
    face = RIGHT;
417

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

    
425
    face = FRONT;
426

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

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

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

    
444
    face = DOWN;
445

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

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

    
456
    face = LEFT;
457

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

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

    
468
    face = BACK;
469

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

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

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

    
487
    return objectString.toString();
488
    }
489

    
490
///////////////////////////////////////////////////////////////////////////////////////////////////
491

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

    
504
///////////////////////////////////////////////////////////////////////////////////////////////////
505

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

    
518
///////////////////////////////////////////////////////////////////////////////////////////////////
519

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