Project

General

Profile

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

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

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
// other objects created from JSON (version2)
119

    
120
  public ObjectSignature(long[] signature)
121
    {
122
    setUpSignature(signature);
123
    }
124

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

    
130
  public ObjectSignature(int lenx, int leny, int lenz, float[][] position)
131
    {
132
    mLayer = new int[] {lenx,leny,lenz};
133
    mSignature = new long[SIZE];
134

    
135
    prepareCubitTouch();
136
    prepareAllCycles();
137

    
138
    for(float[] pos : position)
139
      {
140
      int numCenters = pos.length/3;
141

    
142
      for(int i=0; i<numCenters; i++)
143
        {
144
        float xi = pos[3*i  ];
145
        float yi = pos[3*i+1];
146
        float zi = pos[3*i+2];
147

    
148
        for(int j=i+1; j<numCenters; j++)
149
          {
150
          float xj = pos[3*j  ];
151
          float yj = pos[3*j+1];
152
          float zj = pos[3*j+2];
153

    
154
          if(areNeighbours(xi-xj,yi-yj,zi-zj))
155
            {
156
            float xc = (xi+xj)/2;
157
            float yc = (yi+yj)/2;
158
            float zc = (zi+zj)/2;
159

    
160
            int bitIndex = getIndexOfCubitTouch(xc,yc,zc);
161
            setBit(bitIndex,1);
162
            }
163
          }
164
        }
165
      }
166
    }
167

    
168
///////////////////////////////////////////////////////////////////////////////////////////////////
169

    
170
  public void setSignature(int signature)
171
    {
172
    for(int i=0; i<SIZE-1; i++) mSignature[i]=0;
173
    mSignature[SIZE-1] = signature;
174
    }
175

    
176
///////////////////////////////////////////////////////////////////////////////////////////////////
177

    
178
  public int compareTo(ObjectSignature sig)
179
    {
180
    for(int i=0; i<SIZE; i++)
181
      {
182
      long diff = mSignature[i] - sig.mSignature[i];
183

    
184
           if( diff>0 ) return +1;
185
      else if( diff<0 ) return -1;
186
      }
187

    
188
    return 0;
189
    }
190

    
191
///////////////////////////////////////////////////////////////////////////////////////////////////
192

    
193
  public boolean isEqual(ObjectSignature sig)
194
    {
195
    for(int i=0; i<SIZE; i++)
196
      {
197
      if( mSignature[i] != sig.mSignature[i] ) return false;
198
      }
199

    
200
    return true;
201
    }
202

    
203
///////////////////////////////////////////////////////////////////////////////////////////////////
204

    
205
  public long[] getArray()
206
    {
207
    return mSignature;
208
    }
209

    
210
///////////////////////////////////////////////////////////////////////////////////////////////////
211

    
212
  public String getString()
213
    {
214
    if( mName==null )
215
      {
216
      StringBuilder sb = new StringBuilder();
217

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

    
225
      mName = sb.toString();
226
      }
227

    
228
    return mName;
229
    }
230

    
231
///////////////////////////////////////////////////////////////////////////////////////////////////
232

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

    
243
    return true;
244
    }
245

    
246
///////////////////////////////////////////////////////////////////////////////////////////////////
247

    
248
  public ObjectSignature turn(int axis, int layer, int turn)
249
    {
250
    ObjectSignature ret = new ObjectSignature(this);
251

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

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

    
265
    return ret;
266
    }
267

    
268
///////////////////////////////////////////////////////////////////////////////////////////////////
269

    
270
  private boolean belongsLeft(float[] point, int axis, int layer)
271
    {
272
    return 2*point[axis]+mLayer[axis] == 2*layer;
273
    }
274

    
275
///////////////////////////////////////////////////////////////////////////////////////////////////
276

    
277
  private void cycle2(int[] cyc)
278
    {
279
    int index0 = cyc[0];
280
    int index1 = cyc[1];
281

    
282
    long b0 = getBit(index0);
283
    long b1 = getBit(index1);
284

    
285
    setBit(index1,b0);
286
    setBit(index0,b1);
287
    }
288

    
289
///////////////////////////////////////////////////////////////////////////////////////////////////
290

    
291
  private void cycle4(int turn, int[] cyc)
292
    {
293
    int index0 = cyc[0];
294
    int index1 = cyc[1];
295
    int index2 = cyc[2];
296
    int index3 = cyc[3];
297

    
298
    long b0 = getBit(index0);
299
    long b1 = getBit(index1);
300
    long b2 = getBit(index2);
301
    long b3 = getBit(index3);
302

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

    
323
///////////////////////////////////////////////////////////////////////////////////////////////////
324

    
325
  private void prepareCubitTouch()
326
    {
327
    int numCenters = mLayer[0]*mLayer[1]*mLayer[2];
328
    if( mLayer[0]>1 && mLayer[1]>1 && mLayer[2]>1 ) numCenters -= (mLayer[0]-2)*(mLayer[1]-2)*(mLayer[2]-2);
329

    
330
    float[][] centers = new float[numCenters][];
331
    int index = 0;
332

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

    
341
    ArrayList<float[]> mTouch = new ArrayList<>();
342

    
343
    for(int i=0; i<numCenters; i++)
344
      for(int j=i+1; j<numCenters; j++)
345
        {
346
        float[] c0 = centers[i];
347
        float[] c1 = centers[j];
348

    
349
        float x1 = c0[0];
350
        float y1 = c0[1];
351
        float z1 = c0[2];
352
        float x2 = c1[0];
353
        float y2 = c1[1];
354
        float z2 = c1[2];
355

    
356
        if( areNeighbours(x1-x2,y1-y2,z1-z2) )
357
          {
358
          float xc = (x1+x2)/2;
359
          float yc = (y1+y2)/2;
360
          float zc = (z1+z2)/2;
361

    
362
          float[] touch = new float[] {xc,yc,zc};
363
          mTouch.add(touch);
364
          }
365
        }
366

    
367
    mNumCubitTouches = mTouch.size();
368
    mCubitTouch = new float[mNumCubitTouches][];
369
    for(int i=0; i<mNumCubitTouches; i++) mCubitTouch[i] = mTouch.remove(0);
370

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

    
376
    for(int i=0; i<mNumCubitTouches; i++)
377
      {
378
      float[] ci = mCubitTouch[i];
379
      float val_i = 100*ci[1]-10*ci[2]-ci[0];
380

    
381
      for(int j=i+1; j<mNumCubitTouches; j++)
382
        {
383
        float[] cj = mCubitTouch[j];
384
        float val_j = 100*cj[1]-10*cj[2]-cj[0];
385

    
386
        if( val_j<val_i )
387
          {
388
          mCubitTouch[i] = cj;
389
          mCubitTouch[j] = ci;
390
          val_i = val_j;
391
          ci = cj;
392
          }
393
        }
394
      }
395
    }
396

    
397
///////////////////////////////////////////////////////////////////////////////////////////////////
398

    
399
  private void prepareAllCycles()
400
    {
401
    ArrayList<float[][]> cycles0 = new ArrayList<>();
402
    ArrayList<float[][]> cycles1 = new ArrayList<>();
403
    ArrayList<float[][]> cycles2 = new ArrayList<>();
404

    
405
    mNumLeftCyclesPerLayer = new int[3];
406
    mNumCentCyclesPerLayer = new int[3];
407
    mNumInneCyclesPerLayer = new int[3];
408

    
409
    if( mLayer[1]==mLayer[2] ) generate4Cycles(cycles0,0);
410
    else                       generate2Cycles(cycles0,0);
411
    if( mLayer[0]==mLayer[2] ) generate4Cycles(cycles1,1);
412
    else                       generate2Cycles(cycles1,1);
413
    if( mLayer[0]==mLayer[1] ) generate4Cycles(cycles2,2);
414
    else                       generate2Cycles(cycles2,2);
415

    
416
    mCycles = new int[3][][][];
417

    
418
    mCycles[0] = fillUpCycles(cycles0,0,mLayer[0]);
419
    mCycles[1] = fillUpCycles(cycles1,1,mLayer[1]);
420
    mCycles[2] = fillUpCycles(cycles2,2,mLayer[2]);
421
    }
422

    
423
///////////////////////////////////////////////////////////////////////////////////////////////////
424

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

    
436
      float[] f0 = getCubitTouchOfIndex(i);
437
      float[] f1 = getCubitTouchOfIndex(i0);
438
      float[] f2 = getCubitTouchOfIndex(i1);
439
      float[] f3 = getCubitTouchOfIndex(i2);
440

    
441
      int l = (int)(2*f0[axis]+mLayer[axis]);
442

    
443
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
444
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
445
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
446

    
447
      float[][] cycle = new float[][] { f0,f1,f2,f3 };
448
      cycles.add(cycle);
449
      }
450
    }
451

    
452
///////////////////////////////////////////////////////////////////////////////////////////////////
453

    
454
  private void generate2Cycles(ArrayList<float[][]> cycles, int axis)
455
    {
456
    for(int i=0; i<mNumCubitTouches; i++)
457
      {
458
      int i0 = rotateIndex2(axis,i);
459
      if( i0<=i ) continue;
460

    
461
      float[] f0 = getCubitTouchOfIndex(i);
462
      float[] f1 = getCubitTouchOfIndex(i0);
463

    
464
      int l = (int)(2*f0[axis]+mLayer[axis]);
465

    
466
      if( l==2 ) mNumLeftCyclesPerLayer[axis]++;
467
      if( l==1 ) mNumCentCyclesPerLayer[axis]++;
468
      if( mLayer[axis]>2 && l==3 ) mNumInneCyclesPerLayer[axis]++;
469

    
470
      float[][] cycle = new float[][] { f0,f1 };
471
      cycles.add(cycle);
472
      }
473
    }
474

    
475
///////////////////////////////////////////////////////////////////////////////////////////////////
476

    
477
  private int[][][] fillUpCycles(ArrayList<float[][]> cyc, int axis, int numLayers)
478
    {
479
    int numCycles = cyc.size();
480
    int[] index = new int[numLayers];
481

    
482
    int numFirst = mNumCentCyclesPerLayer[axis];
483
    int numNext  = mNumLeftCyclesPerLayer[axis] + mNumInneCyclesPerLayer[axis];
484
    int numLast  = mNumLeftCyclesPerLayer[axis] + numFirst;
485

    
486
    int[][][] ret = new int[numLayers][][];
487
    ret[          0] = new int[numFirst][];
488
    ret[numLayers-1] = new int[numLast][];
489

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

    
492
    for(int i=0; i<numCycles; i++)
493
      {
494
      float[][] cycle = cyc.remove(0);
495
      int layer = (int)(cycle[0][axis]+numLayers*0.5f);
496

    
497
      if( cycle.length==4 )
498
        {
499
        int i0 = getIndexOfCubitTouch(cycle[0][0],cycle[0][1],cycle[0][2]);
500
        int i1 = getIndexOfCubitTouch(cycle[1][0],cycle[1][1],cycle[1][2]);
501
        int i2 = getIndexOfCubitTouch(cycle[2][0],cycle[2][1],cycle[2][2]);
502
        int i3 = getIndexOfCubitTouch(cycle[3][0],cycle[3][1],cycle[3][2]);
503
        ret[layer][index[layer]] = new int[] {i0,i1,i2,i3};
504
        index[layer]++;
505
        }
506
      else
507
        {
508
        int i0 = getIndexOfCubitTouch(cycle[0][0],cycle[0][1],cycle[0][2]);
509
        int i1 = getIndexOfCubitTouch(cycle[1][0],cycle[1][1],cycle[1][2]);
510
        ret[layer][index[layer]] = new int[] {i0,i1};
511
        index[layer]++;
512
        }
513
      }
514

    
515
    return ret;
516
    }
517

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

    
520
  private int rotateIndex(int axis, int index)
521
    {
522
    float[] touch = getCubitTouchOfIndex(index);
523

    
524
    switch(axis)
525
      {
526
      case 0: return getIndexOfCubitTouch(+touch[0],+touch[2],-touch[1]);
527
      case 1: return getIndexOfCubitTouch(-touch[2],+touch[1],+touch[0]);
528
      case 2: return getIndexOfCubitTouch(+touch[1],-touch[0],+touch[2]);
529
      }
530

    
531
    return -1;
532
    }
533

    
534
///////////////////////////////////////////////////////////////////////////////////////////////////
535

    
536
  private int rotateIndex2(int axis, int index)
537
    {
538
    float[] touch = getCubitTouchOfIndex(index);
539

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

    
547
    return -1;
548
    }
549

    
550
///////////////////////////////////////////////////////////////////////////////////////////////////
551

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

    
560
    return -1;
561
    }
562

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

    
565
  private float[] getCubitTouchOfIndex(int index)
566
    {
567
    return mCubitTouch[index];
568
    }
569

    
570
///////////////////////////////////////////////////////////////////////////////////////////////////
571

    
572
  private boolean areNeighbours(float dx, float dy, float dz)
573
    {
574
    return dx*dx+dy*dy+dz*dz<1.1f;
575
    }
576

    
577
///////////////////////////////////////////////////////////////////////////////////////////////////
578

    
579
  private long getBit(int index)
580
    {
581
    int sigIndex = SIZE-1-(index/64);
582
    return (mSignature[sigIndex]>>(index%64))&0x1;
583
    }
584

    
585
///////////////////////////////////////////////////////////////////////////////////////////////////
586

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