Project

General

Profile

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

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

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

    
65
  public ObjectSignature(ObjectSignature sig)
66
    {
67
    int len = sig.mSignature.length;
68
    mSignature = new long[len];
69
    for(int i=0; i<len; i++) mSignature[i] = sig.mSignature[i];
70

    
71
    mLayer      = sig.mLayer;
72
    mCycles     = sig.mCycles;
73
    mCubitTouch = sig.mCubitTouch;
74
    mName       = sig.mName;
75

    
76
    mNumCubitTouches       = sig.mNumCubitTouches;
77
    mNumCentCyclesPerLayer = sig.mNumCentCyclesPerLayer;
78
    mNumLeftCyclesPerLayer = sig.mNumLeftCyclesPerLayer;
79
    mNumInneCyclesPerLayer = sig.mNumInneCyclesPerLayer;
80
    }
81

    
82
///////////////////////////////////////////////////////////////////////////////////////////////////
83
// built-in objects; objects created from JSON (version1)
84

    
85
  public ObjectSignature(long signature)
86
    {
87
    mSignature = new long[SIZE];
88
    mSignature[SIZE-1] = signature;
89
    }
90

    
91
///////////////////////////////////////////////////////////////////////////////////////////////////
92
// locally created bandaged cuboids created from JSON (version2)
93

    
94
  public ObjectSignature(String shortName, long[] signature)
95
    {
96
    setUpSignature(signature);
97

    
98
    int x = shortName.charAt(0) - '0';
99
    int y = shortName.charAt(1) - '0';
100
    int z = shortName.charAt(2) - '0';
101

    
102
    mLayer = new int[] {x,y,z};
103

    
104
    prepareCubitTouch();
105
    prepareAllCycles();
106
    }
107

    
108
///////////////////////////////////////////////////////////////////////////////////////////////////
109
// BAN*** objects when read from JSON
110

    
111
  public ObjectSignature(int size, long[] signature)
112
    {
113
    setUpSignature(signature);
114

    
115
    mLayer = new int[] {size,size,size};
116

    
117
    prepareCubitTouch();
118
    prepareAllCycles();
119
    }
120

    
121
///////////////////////////////////////////////////////////////////////////////////////////////////
122
// other objects created from JSON (version2)
123

    
124
  public ObjectSignature(long[] signature)
125
    {
126
    setUpSignature(signature);
127
    }
128

    
129
///////////////////////////////////////////////////////////////////////////////////////////////////
130
// Locally created bandaged cuboids 1<=N,M,K<=7
131
// This is the 'Andreas signature' of a bandaged cube.
132
// https://twistypuzzles.com/forum/viewtopic.php?p=415466#p415466
133

    
134
  public ObjectSignature(int lenx, int leny, int lenz, float[][] position)
135
    {
136
    mLayer = new int[] {lenx,leny,lenz};
137
    mSignature = new long[SIZE];
138

    
139
    prepareCubitTouch();
140
    prepareAllCycles();
141

    
142
    for(float[] pos : position)
143
      {
144
      int numCenters = pos.length/3;
145

    
146
      for(int i=0; i<numCenters; i++)
147
        {
148
        float xi = pos[3*i  ];
149
        float yi = pos[3*i+1];
150
        float zi = pos[3*i+2];
151

    
152
        for(int j=i+1; j<numCenters; j++)
153
          {
154
          float xj = pos[3*j  ];
155
          float yj = pos[3*j+1];
156
          float zj = pos[3*j+2];
157

    
158
          if(areNeighbours(xi-xj,yi-yj,zi-zj))
159
            {
160
            float xc = (xi+xj)/2;
161
            float yc = (yi+yj)/2;
162
            float zc = (zi+zj)/2;
163

    
164
            int bitIndex = getIndexOfCubitTouch(xc,yc,zc);
165
            setBit(bitIndex,1);
166
            }
167
          }
168
        }
169
      }
170
    }
171

    
172
///////////////////////////////////////////////////////////////////////////////////////////////////
173

    
174
  public void setSignature(int signature)
175
    {
176
    for(int i=0; i<SIZE-1; i++) mSignature[i]=0;
177
    mSignature[SIZE-1] = signature;
178
    }
179

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

    
182
  public int compareTo(ObjectSignature sig)
183
    {
184
    for(int i=0; i<SIZE; i++)
185
      {
186
      long diff = mSignature[i] - sig.mSignature[i];
187

    
188
           if( diff>0 ) return +1;
189
      else if( diff<0 ) return -1;
190
      }
191

    
192
    return 0;
193
    }
194

    
195
///////////////////////////////////////////////////////////////////////////////////////////////////
196

    
197
  public boolean isEqual(ObjectSignature sig)
198
    {
199
    for(int i=0; i<SIZE; i++)
200
      {
201
      if( mSignature[i] != sig.mSignature[i] ) return false;
202
      }
203

    
204
    return true;
205
    }
206

    
207
///////////////////////////////////////////////////////////////////////////////////////////////////
208

    
209
  public long[] getArray()
210
    {
211
    return mSignature;
212
    }
213

    
214
///////////////////////////////////////////////////////////////////////////////////////////////////
215

    
216
  public String getString()
217
    {
218
    if( mName==null )
219
      {
220
      StringBuilder sb = new StringBuilder();
221

    
222
      for(int i=0; i<SIZE; i++)
223
        {
224
        String sig = String.format("0x%016X", mSignature[i]);
225
        if( i>0 ) sb.append('_');
226
        sb.append(sig);
227
        }
228

    
229
      mName = sb.toString();
230
      }
231

    
232
    return mName;
233
    }
234

    
235
///////////////////////////////////////////////////////////////////////////////////////////////////
236

    
237
  public boolean isUnblockedFromLeft(int axis, int layer)
238
    {
239
    if(layer>0)
240
      for(int index=0; index<mNumCubitTouches; index++)
241
        if( getBit(index)!=0 )
242
          {
243
          float[] touch = getCubitTouchOfIndex(index);
244
          if( belongsLeft(touch,axis,layer) ) return false;
245
          }
246

    
247
    return true;
248
    }
249

    
250
///////////////////////////////////////////////////////////////////////////////////////////////////
251

    
252
  public ObjectSignature turn(int axis, int layer, int turn)
253
    {
254
    ObjectSignature ret = new ObjectSignature(this);
255

    
256
    // I don't understand it, but Firebase shows mCycles is occasionally null here.
257
    if( mCycles!=null && mCycles[axis]!=null )
258
      {
259
      int[][] cycles = mCycles[axis][layer];
260

    
261
      // it can happen that there are no cycles in this layer: 2x1x2 axis 0 layer 0.
262
      if( cycles!=null && cycles.length>0 && cycles[0]!=null )
263
        {
264
        if( cycles[0].length==4 ) for(int[] cyc : cycles) ret.cycle4(turn,cyc);
265
        else                      for(int[] cyc : cycles) ret.cycle2(cyc);
266
        }
267
      }
268

    
269
    return ret;
270
    }
271

    
272
///////////////////////////////////////////////////////////////////////////////////////////////////
273

    
274
  private boolean belongsLeft(float[] point, int axis, int layer)
275
    {
276
    return 2*point[axis]+mLayer[axis] == 2*layer;
277
    }
278

    
279
///////////////////////////////////////////////////////////////////////////////////////////////////
280

    
281
  private void cycle2(int[] cyc)
282
    {
283
    int index0 = cyc[0];
284
    int index1 = cyc[1];
285

    
286
    long b0 = getBit(index0);
287
    long b1 = getBit(index1);
288

    
289
    setBit(index1,b0);
290
    setBit(index0,b1);
291
    }
292

    
293
///////////////////////////////////////////////////////////////////////////////////////////////////
294

    
295
  private void cycle4(int turn, int[] cyc)
296
    {
297
    int index0 = cyc[0];
298
    int index1 = cyc[1];
299
    int index2 = cyc[2];
300
    int index3 = cyc[3];
301

    
302
    long b0 = getBit(index0);
303
    long b1 = getBit(index1);
304
    long b2 = getBit(index2);
305
    long b3 = getBit(index3);
306

    
307
    switch(turn)
308
      {
309
      case 1: setBit(index0,b3);
310
              setBit(index1,b0);
311
              setBit(index2,b1);
312
              setBit(index3,b2);
313
              break;
314
      case 2: setBit(index0,b2);
315
              setBit(index1,b3);
316
              setBit(index2,b0);
317
              setBit(index3,b1);
318
              break;
319
      case 3: setBit(index0,b1);
320
              setBit(index1,b2);
321
              setBit(index2,b3);
322
              setBit(index3,b0);
323
              break;
324
      }
325
    }
326

    
327
///////////////////////////////////////////////////////////////////////////////////////////////////
328

    
329
  private void prepareCubitTouch()
330
    {
331
    int numCenters = mLayer[0]*mLayer[1]*mLayer[2];
332
    if( mLayer[0]>1 && mLayer[1]>1 && mLayer[2]>1 ) numCenters -= (mLayer[0]-2)*(mLayer[1]-2)*(mLayer[2]-2);
333

    
334
    float[][] centers = new float[numCenters][];
335
    int index = 0;
336

    
337
    for(int i=0; i<mLayer[0]; i++)
338
      for(int j=0; j<mLayer[1]; j++)
339
        for(int k=0; k<mLayer[2]; k++)
340
          if( (i==0) || (i==mLayer[0]-1) || (j==0) || (j==mLayer[1]-1) || (k==0) || (k==mLayer[2]-1) )
341
            {
342
            centers[index++] = new float[] { i+0.5f*(1-mLayer[0]), j+0.5f*(1-mLayer[1]), k+0.5f*(1-mLayer[2]) };
343
            }
344

    
345
    ArrayList<float[]> mTouch = new ArrayList<>();
346

    
347
    for(int i=0; i<numCenters; i++)
348
      for(int j=i+1; j<numCenters; j++)
349
        {
350
        float[] c0 = centers[i];
351
        float[] c1 = centers[j];
352

    
353
        float x1 = c0[0];
354
        float y1 = c0[1];
355
        float z1 = c0[2];
356
        float x2 = c1[0];
357
        float y2 = c1[1];
358
        float z2 = c1[2];
359

    
360
        if( areNeighbours(x1-x2,y1-y2,z1-z2) )
361
          {
362
          float xc = (x1+x2)/2;
363
          float yc = (y1+y2)/2;
364
          float zc = (z1+z2)/2;
365

    
366
          float[] touch = new float[] {xc,yc,zc};
367
          mTouch.add(touch);
368
          }
369
        }
370

    
371
    mNumCubitTouches = mTouch.size();
372
    mCubitTouch = new float[mNumCubitTouches][];
373
    for(int i=0; i<mNumCubitTouches; i++) mCubitTouch[i] = mTouch.remove(0);
374

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

    
380
    for(int i=0; i<mNumCubitTouches; i++)
381
      {
382
      float[] ci = mCubitTouch[i];
383
      float val_i = 100*ci[1]-10*ci[2]-ci[0];
384

    
385
      for(int j=i+1; j<mNumCubitTouches; j++)
386
        {
387
        float[] cj = mCubitTouch[j];
388
        float val_j = 100*cj[1]-10*cj[2]-cj[0];
389

    
390
        if( val_j<val_i )
391
          {
392
          mCubitTouch[i] = cj;
393
          mCubitTouch[j] = ci;
394
          val_i = val_j;
395
          ci = cj;
396
          }
397
        }
398
      }
399
    }
400

    
401
///////////////////////////////////////////////////////////////////////////////////////////////////
402

    
403
  private void prepareAllCycles()
404
    {
405
    ArrayList<float[][]> cycles0 = new ArrayList<>();
406
    ArrayList<float[][]> cycles1 = new ArrayList<>();
407
    ArrayList<float[][]> cycles2 = new ArrayList<>();
408

    
409
    mNumLeftCyclesPerLayer = new int[3];
410
    mNumCentCyclesPerLayer = new int[3];
411
    mNumInneCyclesPerLayer = new int[3];
412

    
413
    if( mLayer[1]==mLayer[2] ) generate4Cycles(cycles0,0);
414
    else                       generate2Cycles(cycles0,0);
415
    if( mLayer[0]==mLayer[2] ) generate4Cycles(cycles1,1);
416
    else                       generate2Cycles(cycles1,1);
417
    if( mLayer[0]==mLayer[1] ) generate4Cycles(cycles2,2);
418
    else                       generate2Cycles(cycles2,2);
419

    
420
    mCycles = new int[3][][][];
421

    
422
    mCycles[0] = fillUpCycles(cycles0,0,mLayer[0]);
423
    mCycles[1] = fillUpCycles(cycles1,1,mLayer[1]);
424
    mCycles[2] = fillUpCycles(cycles2,2,mLayer[2]);
425
    }
426

    
427
///////////////////////////////////////////////////////////////////////////////////////////////////
428

    
429
  private void generate4Cycles(ArrayList<float[][]> cycles, int axis)
430
    {
431
    for(int i=0; i<mNumCubitTouches; i++)
432
      {
433
      int i0 = rotateIndex(axis,i);
434
      if( i0<=i ) continue;
435
      int i1 = rotateIndex(axis,i0);
436
      if( i1<=i ) continue;
437
      int i2 = rotateIndex(axis,i1);
438
      if( i2<=i ) continue;
439

    
440
      float[] f0 = getCubitTouchOfIndex(i);
441
      float[] f1 = getCubitTouchOfIndex(i0);
442
      float[] f2 = getCubitTouchOfIndex(i1);
443
      float[] f3 = getCubitTouchOfIndex(i2);
444

    
445
      int l = (int)(2*f0[axis]+mLayer[axis]);
446

    
447
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
448
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
449
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
450

    
451
      float[][] cycle = new float[][] { f0,f1,f2,f3 };
452
      cycles.add(cycle);
453
      }
454
    }
455

    
456
///////////////////////////////////////////////////////////////////////////////////////////////////
457

    
458
  private void generate2Cycles(ArrayList<float[][]> cycles, int axis)
459
    {
460
    for(int i=0; i<mNumCubitTouches; i++)
461
      {
462
      int i0 = rotateIndex2(axis,i);
463
      if( i0<=i ) continue;
464

    
465
      float[] f0 = getCubitTouchOfIndex(i);
466
      float[] f1 = getCubitTouchOfIndex(i0);
467

    
468
      int l = (int)(2*f0[axis]+mLayer[axis]);
469

    
470
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
471
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
472
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
473

    
474
      float[][] cycle = new float[][] { f0,f1 };
475
      cycles.add(cycle);
476
      }
477
    }
478

    
479
///////////////////////////////////////////////////////////////////////////////////////////////////
480

    
481
  private int[][][] fillUpCycles(ArrayList<float[][]> cyc, int axis, int numLayers)
482
    {
483
    int numCycles = cyc.size();
484
    int[] index = new int[numLayers];
485

    
486
    int numFirst = mNumCentCyclesPerLayer[axis];
487
    int numNext  = mNumLeftCyclesPerLayer[axis] + mNumInneCyclesPerLayer[axis];
488
    int numLast  = mNumLeftCyclesPerLayer[axis] + numFirst;
489

    
490
    int[][][] ret = new int[numLayers][][];
491
    ret[          0] = new int[numFirst][];
492
    ret[numLayers-1] = new int[numLast][];
493

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

    
496
    for(int i=0; i<numCycles; i++)
497
      {
498
      float[][] cycle = cyc.remove(0);
499
      int layer = (int)(cycle[0][axis]+numLayers*0.5f);
500

    
501
      if( cycle.length==4 )
502
        {
503
        int i0 = getIndexOfCubitTouch(cycle[0][0],cycle[0][1],cycle[0][2]);
504
        int i1 = getIndexOfCubitTouch(cycle[1][0],cycle[1][1],cycle[1][2]);
505
        int i2 = getIndexOfCubitTouch(cycle[2][0],cycle[2][1],cycle[2][2]);
506
        int i3 = getIndexOfCubitTouch(cycle[3][0],cycle[3][1],cycle[3][2]);
507
        ret[layer][index[layer]] = new int[] {i0,i1,i2,i3};
508
        index[layer]++;
509
        }
510
      else
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
        ret[layer][index[layer]] = new int[] {i0,i1};
515
        index[layer]++;
516
        }
517
      }
518

    
519
    return ret;
520
    }
521

    
522
///////////////////////////////////////////////////////////////////////////////////////////////////
523

    
524
  private int rotateIndex(int axis, int index)
525
    {
526
    float[] touch = getCubitTouchOfIndex(index);
527

    
528
    switch(axis)
529
      {
530
      case 0: return getIndexOfCubitTouch(+touch[0],+touch[2],-touch[1]);
531
      case 1: return getIndexOfCubitTouch(-touch[2],+touch[1],+touch[0]);
532
      case 2: return getIndexOfCubitTouch(+touch[1],-touch[0],+touch[2]);
533
      }
534

    
535
    return -1;
536
    }
537

    
538
///////////////////////////////////////////////////////////////////////////////////////////////////
539

    
540
  private int rotateIndex2(int axis, int index)
541
    {
542
    float[] touch = getCubitTouchOfIndex(index);
543

    
544
    switch(axis)
545
      {
546
      case 0: return getIndexOfCubitTouch(+touch[0],-touch[1],-touch[2]);
547
      case 1: return getIndexOfCubitTouch(-touch[0],+touch[1],-touch[2]);
548
      case 2: return getIndexOfCubitTouch(-touch[0],-touch[1],+touch[2]);
549
      }
550

    
551
    return -1;
552
    }
553

    
554
///////////////////////////////////////////////////////////////////////////////////////////////////
555

    
556
  private int getIndexOfCubitTouch(float x, float y, float z)
557
    {
558
    for(int i=0; i<mNumCubitTouches; i++)
559
      {
560
      float[] touch = mCubitTouch[i];
561
      if( touch[0]==x && touch[1]==y && touch[2]==z ) return i;
562
      }
563

    
564
    return -1;
565
    }
566

    
567
///////////////////////////////////////////////////////////////////////////////////////////////////
568

    
569
  private float[] getCubitTouchOfIndex(int index)
570
    {
571
    return mCubitTouch[index];
572
    }
573

    
574
///////////////////////////////////////////////////////////////////////////////////////////////////
575

    
576
  private boolean areNeighbours(float dx, float dy, float dz)
577
    {
578
    return dx*dx+dy*dy+dz*dz<1.1f;
579
    }
580

    
581
///////////////////////////////////////////////////////////////////////////////////////////////////
582

    
583
  private long getBit(int index)
584
    {
585
    int sigIndex = SIZE-1-(index/64);
586
    return (mSignature[sigIndex]>>(index%64))&0x1;
587
    }
588

    
589
///////////////////////////////////////////////////////////////////////////////////////////////////
590

    
591
  private void setBit(int index, long bit)
592
    {
593
    long diff    = (1L<<(index%64));
594
    int sigIndex = SIZE-1-(index/64);
595
    if( bit!=0 ) mSignature[sigIndex] |= diff;
596
    else         mSignature[sigIndex] &=~diff;
597
    }
598
}
(9-9/13)