Project

General

Profile

Download (18 KB) Statistics
| Branch: | Revision:

distorted-objectlib / src / main / java / org / distorted / objectlib / helpers / ObjectSignature.java @ 8f44228f

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2022 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is proprietary software licensed under an EULA which you should have received      //
7
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
8
///////////////////////////////////////////////////////////////////////////////////////////////////
9

    
10
package org.distorted.objectlib.helpers;
11

    
12
import java.util.ArrayList;
13

    
14
import org.distorted.objectlib.main.ObjectType;
15

    
16
import static org.distorted.objectlib.scrambling.ScrambleStateBandagedCuboid.MAX_SUPPORTED_SIZE;
17

    
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
public class ObjectSignature implements Comparable<ObjectSignature>
21
{
22
  public static final int SIZE = computeNum();
23

    
24
  private long[] mSignature;
25
  private int[] mLayer;
26
  private int[][][][] mCycles;
27
  private float[][] mCubitTouch;
28
  private int mNumCubitTouches;
29
  private int[] mNumLeftCyclesPerLayer;
30
  private int[] mNumCentCyclesPerLayer;
31
  private int[] mNumInneCyclesPerLayer;
32
  private String mName=null;
33

    
34
///////////////////////////////////////////////////////////////////////////////////////////////////
35
// a cube of size N has 12*(N-1)^2 possible places two adjacent cubies can be 'glued'; therefore
36
// the signature must contain ceil( 12*(N-1)^2 / 64 ) bytes.
37

    
38
  private static int computeNum()
39
    {
40
    int max = MAX_SUPPORTED_SIZE-1;
41
    return (int)(0.95f + (3*max*max)/16.0f);
42
    }
43

    
44
///////////////////////////////////////////////////////////////////////////////////////////////////
45

    
46
  private void setUpSignature(long[] signature)
47
    {
48
    int size = signature.length;
49
    int diff = SIZE-size;
50

    
51
    if( diff==0 ) mSignature = signature;
52
    else
53
      {
54
      mSignature = new long[SIZE];
55

    
56
      for(int i=0; i<size; i++)
57
        {
58
        mSignature[diff+i] = signature[i];
59
        }
60
      }
61
    }
62

    
63
///////////////////////////////////////////////////////////////////////////////////////////////////
64
// built-in objects
65

    
66
  public ObjectSignature(ObjectType type)
67
    {
68
    mSignature = new long[SIZE];
69
    mSignature[SIZE-1] = type.ordinal();
70
    }
71

    
72
///////////////////////////////////////////////////////////////////////////////////////////////////
73

    
74
  public ObjectSignature(ObjectSignature sig)
75
    {
76
    int len = sig.mSignature.length;
77
    mSignature = new long[len];
78
    for(int i=0; i<len; i++) mSignature[i] = sig.mSignature[i];
79

    
80
    mLayer      = sig.mLayer;
81
    mCycles     = sig.mCycles;
82
    mCubitTouch = sig.mCubitTouch;
83
    mName       = sig.mName;
84

    
85
    mNumCubitTouches       = sig.mNumCubitTouches;
86
    mNumCentCyclesPerLayer = sig.mNumCentCyclesPerLayer;
87
    mNumLeftCyclesPerLayer = sig.mNumLeftCyclesPerLayer;
88
    mNumInneCyclesPerLayer = sig.mNumInneCyclesPerLayer;
89
    }
90

    
91
///////////////////////////////////////////////////////////////////////////////////////////////////
92
// built-in bandaged 3x3s; objects created from JSON (version1)
93

    
94
  public ObjectSignature(long signature)
95
    {
96
    mSignature = new long[SIZE];
97
    mSignature[SIZE-1] = signature;
98
    }
99

    
100
///////////////////////////////////////////////////////////////////////////////////////////////////
101
// locally created bandaged cuboids created from JSON (version2)
102

    
103
  public ObjectSignature(String shortName, long[] signature)
104
    {
105
    setUpSignature(signature);
106

    
107
    int x = shortName.charAt(0) - '0';
108
    int y = shortName.charAt(1) - '0';
109
    int z = shortName.charAt(2) - '0';
110

    
111
    mLayer = new int[] {x,y,z};
112

    
113
    prepareCubitTouch();
114
    prepareAllCycles();
115
    }
116

    
117
///////////////////////////////////////////////////////////////////////////////////////////////////
118
// BAN*** objects when read from JSON
119

    
120
  public ObjectSignature(int size, long[] signature)
121
    {
122
    setUpSignature(signature);
123

    
124
    mLayer = new int[] {size,size,size};
125

    
126
    prepareCubitTouch();
127
    prepareAllCycles();
128
    }
129

    
130
///////////////////////////////////////////////////////////////////////////////////////////////////
131
// other objects created from JSON (version2)
132

    
133
  public ObjectSignature(long[] signature)
134
    {
135
    setUpSignature(signature);
136
    }
137

    
138
///////////////////////////////////////////////////////////////////////////////////////////////////
139
// Locally created bandaged cuboids 1<=N,M,K<=7
140
// This is the 'Andreas signature' of a bandaged cube.
141
// https://twistypuzzles.com/forum/viewtopic.php?p=415466#p415466
142

    
143
  public ObjectSignature(int lenx, int leny, int lenz, float[][] position)
144
    {
145
    mLayer = new int[] {lenx,leny,lenz};
146
    mSignature = new long[SIZE];
147

    
148
    prepareCubitTouch();
149
    prepareAllCycles();
150

    
151
    for(float[] pos : position)
152
      {
153
      int numCenters = pos.length/3;
154

    
155
      for(int i=0; i<numCenters; i++)
156
        {
157
        float xi = pos[3*i  ];
158
        float yi = pos[3*i+1];
159
        float zi = pos[3*i+2];
160

    
161
        for(int j=i+1; j<numCenters; j++)
162
          {
163
          float xj = pos[3*j  ];
164
          float yj = pos[3*j+1];
165
          float zj = pos[3*j+2];
166

    
167
          if(areNeighbours(xi-xj,yi-yj,zi-zj))
168
            {
169
            float xc = (xi+xj)/2;
170
            float yc = (yi+yj)/2;
171
            float zc = (zi+zj)/2;
172

    
173
            int bitIndex = getIndexOfCubitTouch(xc,yc,zc);
174
            setBit(bitIndex,1);
175
            }
176
          }
177
        }
178
      }
179
    }
180

    
181
///////////////////////////////////////////////////////////////////////////////////////////////////
182

    
183
  public void setSignature(int signature)
184
    {
185
    for(int i=0; i<SIZE-1; i++) mSignature[i]=0;
186
    mSignature[SIZE-1] = signature;
187
    }
188

    
189
///////////////////////////////////////////////////////////////////////////////////////////////////
190

    
191
  public int compareTo(ObjectSignature sig)
192
    {
193
    for(int i=0; i<SIZE; i++)
194
      {
195
      long diff = mSignature[i] - sig.mSignature[i];
196

    
197
           if( diff>0 ) return +1;
198
      else if( diff<0 ) return -1;
199
      }
200

    
201
    return 0;
202
    }
203

    
204
///////////////////////////////////////////////////////////////////////////////////////////////////
205

    
206
  public boolean isEqual(ObjectSignature sig)
207
    {
208
    for(int i=0; i<SIZE; i++)
209
      {
210
      if( mSignature[i] != sig.mSignature[i] ) return false;
211
      }
212

    
213
    return true;
214
    }
215

    
216
///////////////////////////////////////////////////////////////////////////////////////////////////
217

    
218
  public long[] getArray()
219
    {
220
    return mSignature;
221
    }
222

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

    
225
  public String getString()
226
    {
227
    if( mName==null )
228
      {
229
      StringBuilder sb = new StringBuilder();
230

    
231
      for(int i=0; i<SIZE; i++)
232
        {
233
        String sig = String.format("0x%016X", mSignature[i]);
234
        if( i>0 ) sb.append('_');
235
        sb.append(sig);
236
        }
237

    
238
      mName = sb.toString();
239
      }
240

    
241
    return mName;
242
    }
243

    
244
///////////////////////////////////////////////////////////////////////////////////////////////////
245

    
246
  public boolean isUnblockedFromLeft(int axis, int layer)
247
    {
248
    if(layer>0)
249
      for(int index=0; index<mNumCubitTouches; index++)
250
        if( getBit(index)!=0 )
251
          {
252
          float[] touch = getCubitTouchOfIndex(index);
253
          if( belongsLeft(touch,axis,layer) ) return false;
254
          }
255

    
256
    return true;
257
    }
258

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

    
261
  public ObjectSignature turn(int axis, int layer, int turn)
262
    {
263
    ObjectSignature ret = new ObjectSignature(this);
264

    
265
    // I don't understand it, but Firebase shows mCycles is occasionally null here.
266
    if( mCycles!=null && mCycles[axis]!=null )
267
      {
268
      int[][] cycles = mCycles[axis][layer];
269

    
270
      // it can happen that there are no cycles in this layer: 2x1x2 axis 0 layer 0.
271
      if( cycles!=null && cycles.length>0 && cycles[0]!=null )
272
        {
273
        if( cycles[0].length==4 ) for(int[] cyc : cycles) ret.cycle4(turn,cyc);
274
        else                      for(int[] cyc : cycles) ret.cycle2(cyc);
275
        }
276
      }
277

    
278
    return ret;
279
    }
280

    
281
///////////////////////////////////////////////////////////////////////////////////////////////////
282

    
283
  private boolean belongsLeft(float[] point, int axis, int layer)
284
    {
285
    return 2*point[axis]+mLayer[axis] == 2*layer;
286
    }
287

    
288
///////////////////////////////////////////////////////////////////////////////////////////////////
289

    
290
  private void cycle2(int[] cyc)
291
    {
292
    int index0 = cyc[0];
293
    int index1 = cyc[1];
294

    
295
    long b0 = getBit(index0);
296
    long b1 = getBit(index1);
297

    
298
    setBit(index1,b0);
299
    setBit(index0,b1);
300
    }
301

    
302
///////////////////////////////////////////////////////////////////////////////////////////////////
303

    
304
  private void cycle4(int turn, int[] cyc)
305
    {
306
    int index0 = cyc[0];
307
    int index1 = cyc[1];
308
    int index2 = cyc[2];
309
    int index3 = cyc[3];
310

    
311
    long b0 = getBit(index0);
312
    long b1 = getBit(index1);
313
    long b2 = getBit(index2);
314
    long b3 = getBit(index3);
315

    
316
    switch(turn)
317
      {
318
      case 1: setBit(index0,b3);
319
              setBit(index1,b0);
320
              setBit(index2,b1);
321
              setBit(index3,b2);
322
              break;
323
      case 2: setBit(index0,b2);
324
              setBit(index1,b3);
325
              setBit(index2,b0);
326
              setBit(index3,b1);
327
              break;
328
      case 3: setBit(index0,b1);
329
              setBit(index1,b2);
330
              setBit(index2,b3);
331
              setBit(index3,b0);
332
              break;
333
      }
334
    }
335

    
336
///////////////////////////////////////////////////////////////////////////////////////////////////
337

    
338
  private void prepareCubitTouch()
339
    {
340
    int numCenters = mLayer[0]*mLayer[1]*mLayer[2];
341
    if( mLayer[0]>1 && mLayer[1]>1 && mLayer[2]>1 ) numCenters -= (mLayer[0]-2)*(mLayer[1]-2)*(mLayer[2]-2);
342

    
343
    float[][] centers = new float[numCenters][];
344
    int index = 0;
345

    
346
    for(int i=0; i<mLayer[0]; i++)
347
      for(int j=0; j<mLayer[1]; j++)
348
        for(int k=0; k<mLayer[2]; k++)
349
          if( (i==0) || (i==mLayer[0]-1) || (j==0) || (j==mLayer[1]-1) || (k==0) || (k==mLayer[2]-1) )
350
            {
351
            centers[index++] = new float[] { i+0.5f*(1-mLayer[0]), j+0.5f*(1-mLayer[1]), k+0.5f*(1-mLayer[2]) };
352
            }
353

    
354
    ArrayList<float[]> mTouch = new ArrayList<>();
355

    
356
    for(int i=0; i<numCenters; i++)
357
      for(int j=i+1; j<numCenters; j++)
358
        {
359
        float[] c0 = centers[i];
360
        float[] c1 = centers[j];
361

    
362
        float x1 = c0[0];
363
        float y1 = c0[1];
364
        float z1 = c0[2];
365
        float x2 = c1[0];
366
        float y2 = c1[1];
367
        float z2 = c1[2];
368

    
369
        if( areNeighbours(x1-x2,y1-y2,z1-z2) )
370
          {
371
          float xc = (x1+x2)/2;
372
          float yc = (y1+y2)/2;
373
          float zc = (z1+z2)/2;
374

    
375
          float[] touch = new float[] {xc,yc,zc};
376
          mTouch.add(touch);
377
          }
378
        }
379

    
380
    mNumCubitTouches = mTouch.size();
381
    mCubitTouch = new float[mNumCubitTouches][];
382
    for(int i=0; i<mNumCubitTouches; i++) mCubitTouch[i] = mTouch.remove(0);
383

    
384
    // now sort the touches so that the order agrees with 'Andreas signature' as defined here:
385
    // https://twistypuzzles.com/forum/viewtopic.php?p=415466#p415466
386
    // i.e. we need to sort by Y first (increasing) then by Z (decreasing) then by X (decreasing)
387
    // i.e. we need to sort by 100Y-10Z-X (increasing)
388

    
389
    for(int i=0; i<mNumCubitTouches; i++)
390
      {
391
      float[] ci = mCubitTouch[i];
392
      float val_i = 100*ci[1]-10*ci[2]-ci[0];
393

    
394
      for(int j=i+1; j<mNumCubitTouches; j++)
395
        {
396
        float[] cj = mCubitTouch[j];
397
        float val_j = 100*cj[1]-10*cj[2]-cj[0];
398

    
399
        if( val_j<val_i )
400
          {
401
          mCubitTouch[i] = cj;
402
          mCubitTouch[j] = ci;
403
          val_i = val_j;
404
          ci = cj;
405
          }
406
        }
407
      }
408
    }
409

    
410
///////////////////////////////////////////////////////////////////////////////////////////////////
411

    
412
  private void prepareAllCycles()
413
    {
414
    ArrayList<float[][]> cycles0 = new ArrayList<>();
415
    ArrayList<float[][]> cycles1 = new ArrayList<>();
416
    ArrayList<float[][]> cycles2 = new ArrayList<>();
417

    
418
    mNumLeftCyclesPerLayer = new int[3];
419
    mNumCentCyclesPerLayer = new int[3];
420
    mNumInneCyclesPerLayer = new int[3];
421

    
422
    if( mLayer[1]==mLayer[2] ) generate4Cycles(cycles0,0);
423
    else                       generate2Cycles(cycles0,0);
424
    if( mLayer[0]==mLayer[2] ) generate4Cycles(cycles1,1);
425
    else                       generate2Cycles(cycles1,1);
426
    if( mLayer[0]==mLayer[1] ) generate4Cycles(cycles2,2);
427
    else                       generate2Cycles(cycles2,2);
428

    
429
    mCycles = new int[3][][][];
430

    
431
    mCycles[0] = fillUpCycles(cycles0,0,mLayer[0]);
432
    mCycles[1] = fillUpCycles(cycles1,1,mLayer[1]);
433
    mCycles[2] = fillUpCycles(cycles2,2,mLayer[2]);
434
    }
435

    
436
///////////////////////////////////////////////////////////////////////////////////////////////////
437

    
438
  private void generate4Cycles(ArrayList<float[][]> cycles, int axis)
439
    {
440
    for(int i=0; i<mNumCubitTouches; i++)
441
      {
442
      int i0 = rotateIndex(axis,i);
443
      if( i0<=i ) continue;
444
      int i1 = rotateIndex(axis,i0);
445
      if( i1<=i ) continue;
446
      int i2 = rotateIndex(axis,i1);
447
      if( i2<=i ) continue;
448

    
449
      float[] f0 = getCubitTouchOfIndex(i);
450
      float[] f1 = getCubitTouchOfIndex(i0);
451
      float[] f2 = getCubitTouchOfIndex(i1);
452
      float[] f3 = getCubitTouchOfIndex(i2);
453

    
454
      int l = (int)(2*f0[axis]+mLayer[axis]);
455

    
456
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
457
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
458
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
459

    
460
      float[][] cycle = new float[][] { f0,f1,f2,f3 };
461
      cycles.add(cycle);
462
      }
463
    }
464

    
465
///////////////////////////////////////////////////////////////////////////////////////////////////
466

    
467
  private void generate2Cycles(ArrayList<float[][]> cycles, int axis)
468
    {
469
    for(int i=0; i<mNumCubitTouches; i++)
470
      {
471
      int i0 = rotateIndex2(axis,i);
472
      if( i0<=i ) continue;
473

    
474
      float[] f0 = getCubitTouchOfIndex(i);
475
      float[] f1 = getCubitTouchOfIndex(i0);
476

    
477
      int l = (int)(2*f0[axis]+mLayer[axis]);
478

    
479
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
480
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
481
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
482

    
483
      float[][] cycle = new float[][] { f0,f1 };
484
      cycles.add(cycle);
485
      }
486
    }
487

    
488
///////////////////////////////////////////////////////////////////////////////////////////////////
489

    
490
  private int[][][] fillUpCycles(ArrayList<float[][]> cyc, int axis, int numLayers)
491
    {
492
    int numCycles = cyc.size();
493
    int[] index = new int[numLayers];
494

    
495
    int numFirst = mNumCentCyclesPerLayer[axis];
496
    int numNext  = mNumLeftCyclesPerLayer[axis] + mNumInneCyclesPerLayer[axis];
497
    int numLast  = mNumLeftCyclesPerLayer[axis] + numFirst;
498

    
499
    int[][][] ret = new int[numLayers][][];
500
    ret[          0] = new int[numFirst][];
501
    ret[numLayers-1] = new int[numLast][];
502

    
503
    for(int i=1; i<numLayers-1; i++) ret[i] = new int[numNext][];
504

    
505
    for(int i=0; i<numCycles; i++)
506
      {
507
      float[][] cycle = cyc.remove(0);
508
      int layer = (int)(cycle[0][axis]+numLayers*0.5f);
509

    
510
      if( cycle.length==4 )
511
        {
512
        int i0 = getIndexOfCubitTouch(cycle[0][0],cycle[0][1],cycle[0][2]);
513
        int i1 = getIndexOfCubitTouch(cycle[1][0],cycle[1][1],cycle[1][2]);
514
        int i2 = getIndexOfCubitTouch(cycle[2][0],cycle[2][1],cycle[2][2]);
515
        int i3 = getIndexOfCubitTouch(cycle[3][0],cycle[3][1],cycle[3][2]);
516
        ret[layer][index[layer]] = new int[] {i0,i1,i2,i3};
517
        index[layer]++;
518
        }
519
      else
520
        {
521
        int i0 = getIndexOfCubitTouch(cycle[0][0],cycle[0][1],cycle[0][2]);
522
        int i1 = getIndexOfCubitTouch(cycle[1][0],cycle[1][1],cycle[1][2]);
523
        ret[layer][index[layer]] = new int[] {i0,i1};
524
        index[layer]++;
525
        }
526
      }
527

    
528
    return ret;
529
    }
530

    
531
///////////////////////////////////////////////////////////////////////////////////////////////////
532

    
533
  private int rotateIndex(int axis, int index)
534
    {
535
    float[] touch = getCubitTouchOfIndex(index);
536

    
537
    switch(axis)
538
      {
539
      case 0: return getIndexOfCubitTouch(+touch[0],+touch[2],-touch[1]);
540
      case 1: return getIndexOfCubitTouch(-touch[2],+touch[1],+touch[0]);
541
      case 2: return getIndexOfCubitTouch(+touch[1],-touch[0],+touch[2]);
542
      }
543

    
544
    return -1;
545
    }
546

    
547
///////////////////////////////////////////////////////////////////////////////////////////////////
548

    
549
  private int rotateIndex2(int axis, int index)
550
    {
551
    float[] touch = getCubitTouchOfIndex(index);
552

    
553
    switch(axis)
554
      {
555
      case 0: return getIndexOfCubitTouch(+touch[0],-touch[1],-touch[2]);
556
      case 1: return getIndexOfCubitTouch(-touch[0],+touch[1],-touch[2]);
557
      case 2: return getIndexOfCubitTouch(-touch[0],-touch[1],+touch[2]);
558
      }
559

    
560
    return -1;
561
    }
562

    
563
///////////////////////////////////////////////////////////////////////////////////////////////////
564

    
565
  private int getIndexOfCubitTouch(float x, float y, float z)
566
    {
567
    for(int i=0; i<mNumCubitTouches; i++)
568
      {
569
      float[] touch = mCubitTouch[i];
570
      if( touch[0]==x && touch[1]==y && touch[2]==z ) return i;
571
      }
572

    
573
    return -1;
574
    }
575

    
576
///////////////////////////////////////////////////////////////////////////////////////////////////
577

    
578
  private float[] getCubitTouchOfIndex(int index)
579
    {
580
    return mCubitTouch[index];
581
    }
582

    
583
///////////////////////////////////////////////////////////////////////////////////////////////////
584

    
585
  private boolean areNeighbours(float dx, float dy, float dz)
586
    {
587
    return dx*dx+dy*dy+dz*dz<1.1f;
588
    }
589

    
590
///////////////////////////////////////////////////////////////////////////////////////////////////
591

    
592
  private long getBit(int index)
593
    {
594
    int sigIndex = SIZE-1-(index/64);
595
    return (mSignature[sigIndex]>>(index%64))&0x1;
596
    }
597

    
598
///////////////////////////////////////////////////////////////////////////////////////////////////
599

    
600
  private void setBit(int index, long bit)
601
    {
602
    long diff    = (1L<<(index%64));
603
    int sigIndex = SIZE-1-(index/64);
604
    if( bit!=0 ) mSignature[sigIndex] |= diff;
605
    else         mSignature[sigIndex] &=~diff;
606
    }
607
}
(9-9/13)