Project

General

Profile

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

distorted-objectlib / src / main / java / org / distorted / objectlib / main / TwistyObject.java @ 9cb7d66f

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2020 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.main;
11

    
12
import java.io.DataInputStream;
13
import java.io.IOException;
14
import java.io.InputStream;
15
import java.util.Random;
16

    
17
import android.content.SharedPreferences;
18
import android.graphics.Bitmap;
19
import android.graphics.Canvas;
20
import android.graphics.Paint;
21

    
22
import org.distorted.library.effect.Effect;
23
import org.distorted.library.effect.MatrixEffectMove;
24
import org.distorted.library.effect.MatrixEffectQuaternion;
25
import org.distorted.library.effect.MatrixEffectScale;
26
import org.distorted.library.effect.VertexEffectQuaternion;
27
import org.distorted.library.effect.VertexEffectRotate;
28
import org.distorted.library.effect.VertexEffectSink;
29
import org.distorted.library.main.DistortedEffects;
30
import org.distorted.library.main.DistortedLibrary;
31
import org.distorted.library.main.DistortedNode;
32
import org.distorted.library.main.DistortedTexture;
33
import org.distorted.library.main.QuatHelper;
34
import org.distorted.library.mesh.MeshBase;
35
import org.distorted.library.mesh.MeshFile;
36
import org.distorted.library.mesh.MeshJoined;
37
import org.distorted.library.message.EffectListener;
38
import org.distorted.library.type.Dynamic1D;
39
import org.distorted.library.type.Static1D;
40
import org.distorted.library.type.Static3D;
41
import org.distorted.library.type.Static4D;
42

    
43
import org.distorted.objectlib.helpers.FactoryCubit;
44
import org.distorted.objectlib.helpers.FactorySticker;
45
import org.distorted.objectlib.helpers.ObjectFaceShape;
46
import org.distorted.objectlib.helpers.ObjectLibInterface;
47
import org.distorted.objectlib.helpers.ObjectShape;
48
import org.distorted.objectlib.helpers.ObjectSignature;
49
import org.distorted.objectlib.helpers.ObjectSticker;
50
import org.distorted.objectlib.helpers.ObjectStickerOverride;
51
import org.distorted.objectlib.helpers.ObjectVertexEffects;
52
import org.distorted.objectlib.helpers.QuatGroupGenerator;
53
import org.distorted.objectlib.scrambling.ObjectScrambler;
54
import org.distorted.objectlib.json.JsonReader;
55
import org.distorted.objectlib.touchcontrol.*;
56

    
57
import static org.distorted.objectlib.touchcontrol.TouchControl.*;
58

    
59
///////////////////////////////////////////////////////////////////////////////////////////////////
60

    
61
public abstract class TwistyObject
62
  {
63
  public static final int MESH_NICE = 0;
64
  public static final int MESH_FAST = 1;
65

    
66
  public static final int MODE_ICON = 0;
67
  public static final int MODE_NORM = 1;
68

    
69
  public static final int COLOR_YELLOW   = 0xffffff00;
70
  public static final int COLOR_WHITE    = 0xffffffff;
71
  public static final int COLOR_BLUE     = 0xff0000ff;
72
  public static final int COLOR_GREEN    = 0xff00bb00;
73
  public static final int COLOR_RED      = 0xff990000;
74
  public static final int COLOR_ORANGE   = 0xffff6200;
75
  public static final int COLOR_GREY     = 0xff727c7b;
76
  public static final int COLOR_VIOLET   = 0xff7700bb;
77
  public static final int COLOR_STROKE   = 0xff000000;
78
  public static final int COLOR_INTERNAL = 0xff000000;
79

    
80
  public static final int TEXTURE_HEIGHT = 256;
81
  static final int NUM_STICKERS_IN_ROW = 4;
82

    
83
  public static final float SQ2 = (float)Math.sqrt(2);
84
  public static final float SQ3 = (float)Math.sqrt(3);
85
  public static final float SQ5 = (float)Math.sqrt(5);
86
  public static final float SQ6 = (float)Math.sqrt(6);
87

    
88
  private static final float MAX_SIZE_CHANGE = 1.35f;
89
  private static final float MIN_SIZE_CHANGE = 0.75f;
90

    
91
  private static final Static3D CENTER = new Static3D(0,0,0);
92
  private static final int POST_ROTATION_MILLISEC = 500;
93

    
94
  protected float[][] mStickerCoords;
95
  protected Static4D[] mObjectQuats;
96

    
97
  private int[][] mStickerVariants;
98
  private float[] mStickerScales;
99
  private Cubit[] mCubits;
100
  private MeshBase[] mMeshes;
101
  private int mNumCubits, mNumQuats, mNumFaceColors, mNumTextures, mNumOverrides;
102
  private int mNumCubitFaces, mNumStickerTypes;
103
  private Static3D[] mAxis;
104
  private float[][] mCuts;
105
  private int[] mNumCuts;
106
  private float[][] mOrigPos;
107
  private Static4D[] mOrigQuat;
108
  private Static4D[] mMixupModeQuats;
109
  private boolean mIsInMixupMode;
110
  private Static4D mQuat;
111
  private int[] mNumLayers;
112
  private float mSize;
113
  private DistortedEffects mEffects;
114
  private VertexEffectRotate mRotateEffect;
115
  private Dynamic1D mRotationAngle;
116
  private Static3D mRotationAxis;
117
  private Static3D mObjectScale;
118
  private int[] mQuatDebug;
119
  private Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
120
  private DistortedTexture mTexture;
121
  private float mInitScreenRatio;
122
  private int mSolvedFunctionIndex;
123
  private boolean mIsBandaged;
124
  private float mObjectScreenRatio;
125
  private int[][] mSolvedQuats;
126
  private int[][] mQuatMult;
127
  private int[] mTmpQuats;
128
  private int mNumTexRows, mNumTexCols;
129
  private int mRotRowBitmap;
130
  private int mCurrentRotAxis;
131
  private MeshBase mMesh;
132
  private ObjectScrambler mScrambler;
133
  private TouchControl mTouchControl;
134
  private DistortedNode mNode;
135
  private ObjectLibInterface mInterface;
136
  private Bitmap mBitmap;
137
  private ObjectSticker[] mStickers;
138
  private ObjectShape[] mShapes;
139
  private int mNumCubitVariants;
140
  private int[][] mCubitFaceColors;
141
  private int[][] mVariantFaceIsOuter;
142
  private int[][] mBasicAngles;
143
  private int mIconMode;
144
  private InitData mInitData;
145
  private float[][] mRowOffsets;
146
  private boolean[] mBelongs;
147
  private float[] mTmp;
148
  private int mNumPuzzleFaces;
149
  private ObjectStickerOverride[] mStickerOverrides;
150
  private boolean mError;
151
  private String mErrorString;
152
  private int mMaxNumLayers;
153
  private int mNumAxis;
154

    
155
  //////////////////// SOLVED1 ////////////////////////
156

    
157
  private int[] mFaceMap;
158
  private int[][] mScramble;
159
  private int[] mColors;
160

    
161
///////////////////////////////////////////////////////////////////////////////////////////////////
162

    
163
  TwistyObject(InputStream jsonStream, int meshState, int iconMode, Static4D quat, Static3D move, float scale, InputStream meshStream)
164
    {
165
    try
166
      {
167
      JsonReader reader = new JsonReader();
168
      reader.parseJsonFile(jsonStream);
169
      setReader(reader);
170
      mNumLayers = reader.getNumLayers();
171
      mSize      = reader.getSize();
172
      mInitData  = null;
173
      initialize(meshState,iconMode,quat,move,scale,meshStream,true);
174
      mError = false;
175
      mErrorString=null;
176
      }
177
    catch(Exception ex)
178
      {
179
      mError = true;
180
      mErrorString = ex.getMessage();
181
      }
182
    }
183

    
184
///////////////////////////////////////////////////////////////////////////////////////////////////
185

    
186
  public TwistyObject(InitData data, int meshState, int iconMode, float size, Static4D quat, Static3D move, float scale, InputStream meshStream)
187
    {
188
    mNumLayers = data.getNumLayers();
189
    mSize      = size;
190
    mInitData  = data;
191
    initialize(meshState,iconMode,quat,move,scale,meshStream,false);
192
    mError = false;
193
    mErrorString = null;
194
    }
195

    
196
///////////////////////////////////////////////////////////////////////////////////////////////////
197

    
198
  private void debugQuat(Static4D quat, int cubitIndex, float axisX, float axisY, float axisZ, float angle, int place)
199
    {
200
    float[] tracking = mCubits[cubitIndex].getTrackingPoint();
201

    
202
    String problem = (getShortName()+" "+cubitIndex+" "+quat.get0()+" "+quat.get1()+" "+quat.get2());
203
    problem += (" "+angle+" "+place+" "+tracking[0]+" "+tracking[1]+" "+tracking[2]);
204
    problem += (axisX+" "+axisY+" "+axisZ);
205

    
206
    mInterface.reportProblem(problem,true);
207
    }
208

    
209
///////////////////////////////////////////////////////////////////////////////////////////////////
210

    
211
  private void initialize(int meshState, int iconMode, Static4D quat, Static3D move, float scale, InputStream meshStream, boolean fromJSON)
212
    {
213
    mIconMode = iconMode;
214
    mQuat = quat;
215
    mAxis = getRotationAxis();
216
    mInitScreenRatio = getScreenRatio();
217
    mSolvedFunctionIndex = getSolvedFunctionIndex();
218
    mBasicAngles = getBasicAngles();
219
    mObjectQuats = getQuats();
220
    mNumQuats = mObjectQuats.length;
221
    mOrigPos = getCubitPositions(mNumLayers);
222
    mNumPuzzleFaces = getNumFaces();
223
    mRowOffsets = new float[mNumPuzzleFaces][3];
224
    mTmp = new float[4];
225

    
226
    mNumAxis = mAxis.length;
227
    mCuts = getCuts(mNumLayers);
228
    mNumCuts = new int[mNumAxis];
229
    mMaxNumLayers = -1;
230
    for(int i=0; i<mNumAxis; i++)
231
      {
232
      if( mMaxNumLayers<mNumLayers[i] ) mMaxNumLayers = mNumLayers[i];
233
      mNumCuts[i] = (mCuts==null || mCuts[i]==null ? 0 : mCuts[i].length);
234
      }
235

    
236
    mNumCubits = mOrigPos.length;
237
    mNumFaceColors = getNumFaceColors();
238
    mBelongs = new boolean[mNumCubits];
239

    
240
    int scramblingType = getScrambleType();
241
    int[][] edges = getScrambleEdges();
242
    int[][] algorithms = getScrambleAlgorithms();
243

    
244
    // if( edges!=null )      print_table("EDGES", edges);
245
    // if( algorithms!=null ) print_table("ALGOS", algorithms);
246

    
247
    mScrambler = new ObjectScrambler(scramblingType,mNumAxis,mNumLayers,algorithms,edges);
248

    
249
    boolean bandaged=false;
250

    
251
    for( int c=0; c<mNumCubits; c++)
252
      {
253
      if( mOrigPos[c].length>3 )
254
        {
255
        bandaged=true;
256
        break;
257
        }
258
      }
259
    mIsBandaged = bandaged;
260
    mQuatDebug = new int[mNumCubits];
261

    
262
    mRotationAngle= new Dynamic1D();
263
    mRotationAxis = new Static3D(1,0,0);
264
    mRotateEffect = new VertexEffectRotate(mRotationAngle, mRotationAxis, CENTER);
265

    
266
    mRotationAngleStatic = new Static1D(0);
267
    mRotationAngleMiddle = new Static1D(0);
268
    mRotationAngleFinal  = new Static1D(0);
269

    
270
    mObjectScale = new Static3D(scale,scale,scale);
271
    setObjectRatioNow(scale,720);
272

    
273
    mEffects = new DistortedEffects();
274
    createQuaternionEffects();
275

    
276
    MatrixEffectScale scaleEffect = new MatrixEffectScale(mObjectScale);
277
    MatrixEffectQuaternion quatEffect = new MatrixEffectQuaternion(mQuat, CENTER);
278
    MatrixEffectMove moveEffect = new MatrixEffectMove(move);
279

    
280
    boolean fromDMESH = (meshStream!=null && meshState==MESH_NICE);
281
    getQuatsAndShapes(fromDMESH,fromJSON);
282
    createMeshAndCubits(meshStream,meshState,fromDMESH);
283
    setUpTextures(fromDMESH,fromJSON);
284
    createDataStructuresForSolved();
285

    
286
    mEffects.apply(mRotateEffect);
287
    mEffects.apply(quatEffect);
288
    mEffects.apply(scaleEffect);
289
    mEffects.apply(moveEffect);
290

    
291
    mNode = new DistortedNode(mTexture,mEffects,mMesh);
292
    }
293

    
294
///////////////////////////////////////////////////////////////////////////////////////////////////
295

    
296
  private static void print_table(String mess, int[][] table)
297
    {
298
    android.util.Log.e("D", mess);
299

    
300
    int len = table.length;
301

    
302
    for(int i=0; i<len; i++)
303
      {
304
      String m = "";
305
      int l = table[i].length;
306
      for(int j=0; j<l; j++) m += (" "+table[i][j]);
307
      android.util.Log.e("D", m);
308
      }
309
    }
310

    
311
///////////////////////////////////////////////////////////////////////////////////////////////////
312

    
313
  private void createQuaternionEffects()
314
    {
315
    if( mNumQuats<=ObjectControl.MAX_QUATS )
316
      {
317
      mIsInMixupMode = false;
318

    
319
      for( int q=0; q<mNumQuats; q++)
320
        {
321
        VertexEffectQuaternion vq = new VertexEffectQuaternion(mObjectQuats[q],CENTER);
322
        vq.setMeshAssociation(0,q);
323
        mEffects.apply(vq);
324
        }
325
      }
326
    else if( mNumCubits<=ObjectControl.MAX_QUATS )
327
      {
328
      mIsInMixupMode = true;
329
      mMixupModeQuats = new Static4D[mNumCubits];
330

    
331
      for( int q=0; q<mNumCubits; q++)
332
        {
333
        mMixupModeQuats[q] = new Static4D(mObjectQuats[0]);
334
        VertexEffectQuaternion vq = new VertexEffectQuaternion(mMixupModeQuats[q],CENTER);
335
        vq.setMeshAssociation(0,q);
336
        mEffects.apply(vq);
337
        }
338
      }
339
    else
340
      {
341
      android.util.Log.e("D", "object has too many quaternions ("+mNumQuats+") or too many cubits ("+mNumCubits+")");
342
      }
343
    }
344

    
345
///////////////////////////////////////////////////////////////////////////////////////////////////
346

    
347
  private Static3D getPos(float[] origPos)
348
    {
349
    int len = origPos.length/3;
350
    float sumX = 0.0f;
351
    float sumY = 0.0f;
352
    float sumZ = 0.0f;
353

    
354
    for(int i=0; i<len; i++)
355
      {
356
      sumX += origPos[3*i  ];
357
      sumY += origPos[3*i+1];
358
      sumZ += origPos[3*i+2];
359
      }
360

    
361
    sumX /= len;
362
    sumY /= len;
363
    sumZ /= len;
364

    
365
    return new Static3D(sumX,sumY,sumZ);
366
    }
367

    
368
///////////////////////////////////////////////////////////////////////////////////////////////////
369

    
370
  private void createOuterFaces()
371
    {
372
    for(int v=0; v<mNumCubitVariants; v++)
373
      {
374
      int[][] indices = mShapes[v].getVertIndices();
375
      int faces = indices.length;
376
      mVariantFaceIsOuter[v] = new int[faces];
377
      }
378

    
379
    for( int cubit=0; cubit<mNumCubits; cubit++)
380
      {
381
      int variant = getCubitVariant(cubit,mNumLayers);
382
      int[][] indices = mShapes[variant].getVertIndices();
383
      int numFaces = indices.length;
384

    
385
      for(int face=0; face<numFaces; face++)
386
        if( getCubitFaceColor(cubit,face)>=0 )
387
          {
388
          mVariantFaceIsOuter[variant][face] = 1;
389
          }
390
      }
391
    }
392

    
393
///////////////////////////////////////////////////////////////////////////////////////////////////
394

    
395
  private void getQuatsAndShapes(boolean fromDMESH, boolean fromJSON)
396
    {
397
    mNumCubitVariants = getNumCubitVariants(mNumLayers);
398

    
399
    if( !fromDMESH || !fromJSON )
400
      {
401
      FactoryCubit factory = FactoryCubit.getInstance();
402
      factory.clear();
403

    
404
      mOrigQuat = new Static4D[mNumCubits];
405
      for(int i=0; i<mNumCubits; i++) mOrigQuat[i] = getCubitQuats(i,mNumLayers);
406

    
407
      mShapes = new ObjectShape[mNumCubitVariants];
408
      for(int i=0; i<mNumCubitVariants; i++) mShapes[i] = getObjectShape(i);
409
      mNumCubitFaces = ObjectShape.computeNumComponents(mShapes);
410
      mVariantFaceIsOuter = new int[mNumCubitVariants][];
411

    
412
      if( !fromJSON )
413
        {
414
        mCubitFaceColors = ObjectShape.computeColors(mShapes,mOrigPos,mOrigQuat,this);
415
        createOuterFaces();
416
        }
417

    
418
      if( fromDMESH )
419
        {
420
        for(int i=0; i<mNumCubitVariants; i++) factory.createNewFaceTransform(mShapes[i], mVariantFaceIsOuter[i]);
421
        }
422
      }
423
    }
424

    
425
///////////////////////////////////////////////////////////////////////////////////////////////////
426

    
427
  private void createMeshAndCubits(InputStream stream, int meshState, boolean fromDMESH)
428
    {
429
    mCubits = new Cubit[mNumCubits];
430

    
431
    if( fromDMESH )
432
      {
433
      DataInputStream dos = new DataInputStream(stream);
434
      mMesh = new MeshFile(dos);
435

    
436
      try
437
        {
438
        stream.close();
439
        }
440
      catch(IOException e)
441
        {
442
        android.util.Log.e("meshFile", "Error closing InputStream: "+e);
443
        }
444
      }
445
    else
446
      {
447
      MeshBase[] cubitMesh = new MeshBase[mNumCubits];
448

    
449
      for(int i=0; i<mNumCubits; i++)
450
        {
451
        cubitMesh[i] = createCubitMesh(i,mNumLayers,meshState,mNumCubitFaces);
452
        Static3D pos = getPos(mOrigPos[i]);
453
        cubitMesh[i].apply(new MatrixEffectMove(pos),1,0);
454
        }
455

    
456
      mMesh = new MeshJoined(cubitMesh);
457

    
458
      float pillowCoeff = getPillowCoeff();
459

    
460
      if( pillowCoeff!=1.0f )
461
        {
462
        float radius = getCircumscribedRadius();
463
        Static1D coeff = new Static1D(pillowCoeff);
464
        Static4D region= new Static4D(0,0,0,radius);
465
        VertexEffectSink sink = new VertexEffectSink(coeff,CENTER,region);
466
        mMesh.apply(sink);
467
        }
468
      }
469

    
470
    for(int i=0; i<mNumCubits; i++)
471
      {
472
      mCubits[i] = new Cubit(this,mOrigPos[i],mNumAxis,mMaxNumLayers,i);
473
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
474
      }
475
    }
476

    
477
///////////////////////////////////////////////////////////////////////////////////////////////////
478

    
479
  private MeshBase createCubitMesh(int cubit, int[] numLayers, int meshState, int numComponents)
480
    {
481
    int variant = getCubitVariant(cubit,numLayers);
482

    
483
    if( mMeshes==null ) mMeshes = new MeshBase[mNumCubitVariants];
484

    
485
    if( mMeshes[variant]==null )
486
      {
487
      ObjectFaceShape faceShape = getObjectFaceShape(variant);
488
      ObjectVertexEffects effects = getVertexEffects(variant);
489
      FactoryCubit factory = FactoryCubit.getInstance();
490
      factory.createNewFaceTransform(mShapes[variant],mVariantFaceIsOuter[variant]);
491
      mMeshes[variant] = factory.createRoundedSolid(mShapes[variant],faceShape,effects, meshState, numComponents);
492
      }
493

    
494
    MeshBase mesh = mMeshes[variant].copy(true);
495
    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( mOrigQuat[cubit], CENTER );
496
    mesh.apply(quat,0xffffffff,0);
497

    
498
    return mesh;
499
    }
500

    
501
///////////////////////////////////////////////////////////////////////////////////////////////////
502

    
503
  private void setUpTextures(boolean fromDMESH, boolean fromJSON)
504
    {
505
    mTexture = new DistortedTexture();
506

    
507
    if( fromJSON )
508
      {
509
      mNumStickerTypes = getNumStickerTypes();
510
      mNumCubitFaces = getNumCubitFaces();
511
      }
512
    else
513
      {
514
      FactoryCubit factory = FactoryCubit.getInstance();
515
      mStickerCoords   = factory.getStickerCoords();
516
      mStickerVariants = factory.getStickerVariants();
517
      mStickerScales   = factory.getStickerScales();
518
      adjustStickerCoords();
519
      mNumStickerTypes = (mStickerCoords==null ? 0 : mStickerCoords.length);
520
      }
521

    
522
    mStickerOverrides = getStickerOverrides();
523
    mNumOverrides = mStickerOverrides==null ? 0 : mStickerOverrides.length;
524

    
525
    mNumTextures= mNumFaceColors*mNumStickerTypes + mNumOverrides;
526
    mNumTexCols = NUM_STICKERS_IN_ROW;
527
    mNumTexRows = (mNumTextures+1)/NUM_STICKERS_IN_ROW;
528
    if( mNumTexCols*mNumTexRows < mNumTextures+1 ) mNumTexRows++;
529

    
530
    if( !fromDMESH || shouldResetTextureMaps() ) resetAllTextureMaps();
531
    else overrideCubitFaceColor();
532

    
533
    setTexture();
534
    }
535

    
536
///////////////////////////////////////////////////////////////////////////////////////////////////
537

    
538
  private int getMultQuat(int index1, int index2)
539
    {
540
    if( mQuatMult==null )
541
      {
542
      mQuatMult = new int[mNumQuats][mNumQuats];
543

    
544
      for(int i=0; i<mNumQuats; i++)
545
        for(int j=0; j<mNumQuats; j++) mQuatMult[i][j] = -1;
546
      }
547

    
548
    if( index1<mNumQuats && index2<mNumQuats )
549
      {
550
      if( mQuatMult[index1][index2]==-1 ) mQuatMult[index1][index2] = mulQuat(index1,index2);
551
      return mQuatMult[index1][index2];
552
      }
553

    
554
    return -1;
555
    }
556

    
557
///////////////////////////////////////////////////////////////////////////////////////////////////
558

    
559
  public InitData getInitData()
560
    {
561
    return mInitData;
562
    }
563

    
564
///////////////////////////////////////////////////////////////////////////////////////////////////
565

    
566
  public boolean isInIconMode()
567
    {
568
    return mIconMode==MODE_ICON;
569
    }
570

    
571
///////////////////////////////////////////////////////////////////////////////////////////////////
572

    
573
  public int getVariantStickerShape(int variant, int face)
574
    {
575
    return face>=mStickerVariants[variant].length ? -1 : mStickerVariants[variant][face];
576
    }
577

    
578
///////////////////////////////////////////////////////////////////////////////////////////////////
579

    
580
  public boolean shouldResetTextureMaps()
581
    {
582
    return false;
583
    }
584

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

    
587
  public int[][] getScrambleAlgorithms()
588
    {
589
    int num=0;
590

    
591
    for (int[] basic : mBasicAngles)
592
      for (int i : basic) num += (i-1);
593

    
594
    int[][] ret = new int[num][3];
595
    int index = 0;
596

    
597
    for(int i=0; i<mNumAxis; i++)
598
      for(int j=0; j<mNumLayers[i]; j++)
599
        {
600
        int N = mBasicAngles[i][j];
601
        int NEG = (1-N)/2;
602
        int POS = N/2;
603

    
604
        for(int k=NEG; k<=-1; k++)
605
          {
606
          ret[index][0] = i;
607
          ret[index][1] = j;
608
          ret[index][2] = k;
609
          index++;
610
          }
611

    
612
        for(int k=1; k<=POS; k++)
613
          {
614
          ret[index][0] = i;
615
          ret[index][1] = j;
616
          ret[index][2] = k;
617
          index++;
618
          }
619
        }
620

    
621
    return ret;
622
    }
623

    
624
///////////////////////////////////////////////////////////////////////////////////////////////////
625

    
626
  private void createDataStructuresForSolved()
627
    {
628
    mTmpQuats = new int[mNumQuats];
629
    mSolvedQuats = getSolvedQuats();
630
    }
631

    
632
///////////////////////////////////////////////////////////////////////////////////////////////////
633
// This is used to build internal data structures for the generic 'isSolved()'
634
//
635
// if this is an internal cubit (all faces black): return -1
636
// if this is a face cubit (one non-black face): return the color index of the only non-black face.
637
// Color index, i.e. the index into the 'FACE_COLORS' table.
638
// else (edge or corner cubit, more than one non-black face): return -2.
639

    
640
  protected int retCubitSolvedStatus(int cubit)
641
    {
642
    int numNonBlack=0, nonBlackIndex=-1, stiShape, cubColor;
643
    int variant = getCubitVariant(cubit,mNumLayers);
644

    
645
    for(int face=0; face<mNumCubitFaces; face++)
646
      {
647
      stiShape = getVariantStickerShape(variant,face);
648
      int numFaces = mCubitFaceColors[cubit].length;
649
      cubColor = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
650

    
651
      if( stiShape>=0 && cubColor>=0 )
652
        {
653
        numNonBlack++;
654
        nonBlackIndex = cubColor;
655
        }
656
      }
657

    
658
    if( numNonBlack==0 ) return -1;
659
    if( numNonBlack>=2 ) return -2;
660

    
661
    return nonBlackIndex;
662
    }
663

    
664
///////////////////////////////////////////////////////////////////////////////////////////////////
665

    
666
  private boolean sticksOut(Static3D[] faceAxis, float[] dist, float x, float y, float z )
667
    {
668
    final float MAXERR = 0.05f;
669
    int numAxis = dist.length;
670

    
671
    for(int i=0; i<numAxis; i++)
672
      {
673
      Static3D ax = faceAxis[i];
674
      float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
675
      if( len>mSize*dist[i]+MAXERR ) return true;
676
      }
677

    
678
    return false;
679
    }
680

    
681
///////////////////////////////////////////////////////////////////////////////////////////////////
682

    
683
  private boolean doesNotStickOut(int variant, float px, float py, float pz, float[] tmp, Static4D quat)
684
    {
685
    float[][] vertices = mShapes[variant].getVertices();
686
    Static3D[] axis = getFaceAxis();
687
    float[] dist3D = getDist3D(mNumLayers);
688

    
689
    for( float[] vertex : vertices)
690
      {
691
      float x = vertex[0];
692
      float y = vertex[1];
693
      float z = vertex[2];
694

    
695
      QuatHelper.rotateVectorByQuat(tmp, x, y, z, 1, quat);
696

    
697
      float mx = tmp[0] + px;
698
      float my = tmp[1] + py;
699
      float mz = tmp[2] + pz;
700

    
701
      if( sticksOut(axis, dist3D, mx,my,mz) ) return false;
702
      }
703

    
704
    return true;
705
    }
706

    
707
///////////////////////////////////////////////////////////////////////////////////////////////////
708

    
709
  private float computeAvg(float[] pos, int offset)
710
    {
711
    int len = pos.length/3;
712
    float ret=0.0f;
713
    for(int i=0; i<len; i++) ret += pos[3*i+offset];
714
    ret /= len;
715

    
716
    return ret;
717
    }
718

    
719
///////////////////////////////////////////////////////////////////////////////////////////////////
720

    
721
  protected void displayCubitQuats()
722
    {
723
    StringBuilder builder = new StringBuilder();
724
    float[] tmp = new float[4];
725
    float ERR = 0.01f;
726

    
727
    for(int cubit=0; cubit<mNumCubits; cubit++)
728
      {
729
      builder.append(cubit);
730
      builder.append(" : ");
731

    
732
      int refCubit,variant = getCubitVariant(cubit,mNumLayers);
733

    
734
      for(refCubit=0; refCubit<mNumCubits; refCubit++)
735
        {
736
        if( getCubitVariant(refCubit,mNumLayers)==variant ) break;
737
        }
738

    
739
      float[] curpos = mOrigPos[cubit];
740
      float[] refpos = mOrigPos[refCubit];
741
      float refX = computeAvg(refpos,0);
742
      float refY = computeAvg(refpos,1);
743
      float refZ = computeAvg(refpos,2);
744
      float curX = computeAvg(curpos,0);
745
      float curY = computeAvg(curpos,1);
746
      float curZ = computeAvg(curpos,2);
747

    
748
      for(int quat=0; quat<mNumQuats; quat++)
749
        {
750
        QuatHelper.rotateVectorByQuat(tmp,refX,refY,refZ,0,mObjectQuats[quat]);
751

    
752
        float dx = tmp[0]-curX;
753
        float dy = tmp[1]-curY;
754
        float dz = tmp[2]-curZ;
755

    
756
        if( dx>-ERR && dx<ERR && dy>-ERR && dy<ERR && dz>-ERR && dz<ERR )
757
          {
758
          if( doesNotStickOut(variant,curX,curY,curZ,tmp,mObjectQuats[quat]) )
759
            {
760
            builder.append(quat);
761
            builder.append(',');
762
            }
763
          else
764
            {
765
            android.util.Log.e("D", "cubit: "+cubit+" quat: "+quat+" : center correct, but shape sticks out");
766
            }
767
          }
768
        }
769

    
770
      builder.append('\n');
771
      }
772

    
773
    android.util.Log.e("D", "cubitQuats: \n"+builder );
774
    }
775

    
776
///////////////////////////////////////////////////////////////////////////////////////////////////
777

    
778
  protected int[] buildSolvedQuats(Static3D faceAx)
779
    {
780
    final float MAXD = 0.0001f;
781
    float x = faceAx.get0();
782
    float y = faceAx.get1();
783
    float z = faceAx.get2();
784
    float a,dx,dy,dz,qx,qy,qz;
785
    Static4D quat;
786
    int place = 0;
787

    
788
    for(int q=1; q<mNumQuats; q++)
789
      {
790
      quat = mObjectQuats[q];
791
      qx = quat.get0();
792
      qy = quat.get1();
793
      qz = quat.get2();
794

    
795
           if( x!=0.0f ) { a = qx/x; }
796
      else if( y!=0.0f ) { a = qy/y; }
797
      else               { a = qz/z; }
798

    
799
      dx = a*x-qx;
800
      dy = a*y-qy;
801
      dz = a*z-qz;
802

    
803
      if( dx>-MAXD && dx<MAXD && dy>-MAXD && dy<MAXD && dz>-MAXD && dz<MAXD )
804
        {
805
        mTmpQuats[place++] = q;
806
        }
807
      }
808

    
809
    if( place!=0 )
810
      {
811
      int[] ret = new int[place];
812
      System.arraycopy(mTmpQuats,0,ret,0,place);
813
      return ret;
814
      }
815

    
816
    return null;
817
    }
818

    
819
///////////////////////////////////////////////////////////////////////////////////////////////////
820

    
821
  public int getCubitRotationType(int cubit)
822
    {
823
    return Cubit.TYPE_NORMAL;
824
    }
825

    
826
///////////////////////////////////////////////////////////////////////////////////////////////////
827

    
828
  float[] getTrackingPoint(int cubitIndex, int cubitType)
829
    {
830
    if( cubitType!=Cubit.TYPE_NORMAL )
831
      {
832
      int variant = getCubitVariant(cubitIndex,mNumLayers);
833

    
834
      // object must have been created from JSON
835
      if( mVariantFaceIsOuter==null || mVariantFaceIsOuter[variant]==null )
836
        {
837
        mVariantFaceIsOuter = getVariantFaceIsOuter();
838
        }
839
      if( mShapes==null )
840
        {
841
        mShapes = new ObjectShape[mNumCubitVariants];
842
        }
843
      if( mShapes[variant]==null )
844
        {
845
        mShapes[variant] = getObjectShape(variant);
846
        }
847
      if( mOrigQuat==null )
848
        {
849
        mOrigQuat = new Static4D[mNumCubits];
850
        }
851
      if( mOrigQuat[cubitIndex]==null )
852
        {
853
        mOrigQuat[cubitIndex] = getCubitQuats(cubitIndex,mNumLayers);
854
        }
855

    
856
      int[][] indices = mShapes[variant].getVertIndices();
857
      int outer=-1, faces = indices.length;
858

    
859
      for(int i=0; i<faces; i++)
860
        {
861
        if( mVariantFaceIsOuter[variant][i]==1 )
862
          {
863
          outer=i;
864
          break;
865
          }
866
        }
867

    
868
      if( outer>=0 )
869
        {
870
        int vertIndex = indices[outer][0];
871
        float[] vertices = mShapes[variant].getVertices()[vertIndex];
872
        float[] ret = new float[3];
873
        float[] curpos = mOrigPos[cubitIndex];
874
        Static4D quat = mOrigQuat[cubitIndex];
875
        QuatHelper.rotateVectorByQuat(mTmp, vertices[0], vertices[1], vertices[2], 1, quat);
876

    
877
        ret[0] = mTmp[0]+computeAvg(curpos,0);
878
        ret[1] = mTmp[1]+computeAvg(curpos,1);
879
        ret[2] = mTmp[2]+computeAvg(curpos,2);
880

    
881
        return ret;
882
        }
883
      else
884
        {
885
        android.util.Log.e("D", "Error in getTrackingPoint: no outer face??");
886
        }
887
      }
888

    
889
    return null;
890
    }
891

    
892
///////////////////////////////////////////////////////////////////////////////////////////////////
893

    
894
  public int computeCurrentPuzzleFace(int type, float[] vertex)
895
    {
896
    if( type!=Cubit.TYPE_NORMAL )
897
      {
898
      Static3D[] axis = getFaceAxis();
899
      float[] dist3D = getDist3D(mNumLayers);
900
      final float MAXERR = 0.98f;
901
      int numAxis = axis.length;
902
      float x = vertex[0];
903
      float y = vertex[1];
904
      float z = vertex[2];
905

    
906
      for(int i=0; i<numAxis; i++)
907
        {
908
        Static3D ax = axis[i];
909
        float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
910
        if( len>mSize*dist3D[i]*MAXERR ) return i;
911
        }
912

    
913
      return -2;
914
      }
915

    
916
    return -1;
917
    }
918

    
919
///////////////////////////////////////////////////////////////////////////////////////////////////
920

    
921
  public float[] getCubitRowOffset(int cubitIndex)
922
    {
923
    return null;
924
    }
925

    
926
///////////////////////////////////////////////////////////////////////////////////////////////////
927

    
928
  void setRotationRowOffset(int puzzleFace, float[] offset)
929
    {
930
    mRowOffsets[puzzleFace][0] = offset[0];
931
    mRowOffsets[puzzleFace][1] = offset[1];
932
    mRowOffsets[puzzleFace][2] = offset[2];
933
    }
934

    
935
///////////////////////////////////////////////////////////////////////////////////////////////////
936

    
937
  int getNumAxis()
938
    {
939
    return mNumAxis;
940
    }
941

    
942
///////////////////////////////////////////////////////////////////////////////////////////////////
943

    
944
  public int[][] getSolvedQuats()
945
    {
946
    int[] groups = new int[mNumCubits];
947
    int numGroups = 1;
948
    int numFirst  = 0;
949

    
950
    for(int cubit=0; cubit<mNumCubits; cubit++)
951
      {
952
      groups[cubit] = retCubitSolvedStatus(cubit);
953
      if( groups[cubit]>=0 ) numGroups++;
954
      else                   numFirst++;
955
      }
956

    
957
    int firstIndex = 1;
958
    int groupIndex = 1;
959
    int[][] solvedQuats = new int[numGroups][];
960
    solvedQuats[0] = new int[1+numFirst];
961
    solvedQuats[0][0] = numFirst;
962
    Static3D[] axis = getFaceAxis();
963

    
964
    for(int cubit=0; cubit<mNumCubits; cubit++)
965
      {
966
      int group = groups[cubit];
967

    
968
      if( group<0 )
969
        {
970
        solvedQuats[0][firstIndex] = cubit;
971
        firstIndex++;
972
        }
973
      else
974
        {
975
        int[] quats = buildSolvedQuats(axis[group]);
976
        int len = quats==null ? 0 : quats.length;
977
        solvedQuats[groupIndex] = new int[2+len];
978
        solvedQuats[groupIndex][0] = 1;
979
        solvedQuats[groupIndex][1] = cubit;
980
        for(int i=0; i<len; i++) solvedQuats[groupIndex][i+2] = quats[i];
981
        groupIndex++;
982
        }
983
      }
984
/*
985
    String dbg = "SOLVED GROUPS:\n";
986

    
987
    for(int g=0; g<numGroups; g++)
988
      {
989
      int len = solvedQuats[g].length;
990
      for(int i=0; i<len; i++) dbg += (" "+solvedQuats[g][i]);
991
      dbg+="\n";
992
      }
993

    
994
    android.util.Log.e("D", dbg);
995
*/
996
    return solvedQuats;
997
    }
998

    
999
///////////////////////////////////////////////////////////////////////////////////////////////////
1000

    
1001
  public int getSolvedFunctionIndex()
1002
    {
1003
    return 0;
1004
    }
1005

    
1006
///////////////////////////////////////////////////////////////////////////////////////////////////
1007
// special SolvedQuats for the case where there are no corner of edge cubits.
1008
// first row {0} - means there are no corners or edges.
1009
// each next defines all cubits of a singe face (numCubits, firstCubit, cubit1,..,cubitN-1, quat0,..., quatM
1010

    
1011
  private boolean isSolvedCentersOnly()
1012
    {
1013
    int numGroups = mSolvedQuats.length;
1014

    
1015
    for(int group=1; group<numGroups; group++)
1016
      {
1017
      int numEntries= mSolvedQuats[group].length;
1018
      int numCubits = mSolvedQuats[group][0];
1019
      int firstCubit= mSolvedQuats[group][1];
1020
      int firstQuat = mCubits[firstCubit].mQuatIndex;
1021

    
1022
      for(int cubit=2; cubit<=numCubits; cubit++)
1023
        {
1024
        int currCubit= mSolvedQuats[group][cubit];
1025
        int currQuat = mCubits[currCubit].mQuatIndex;
1026
        boolean isGood= (firstQuat==currQuat);
1027

    
1028
        for(int q=numCubits+1; !isGood && q<numEntries; q++)
1029
          {
1030
          int quat = mSolvedQuats[group][q];
1031
          if( firstQuat == getMultQuat(currQuat,quat) ) isGood = true;
1032
          }
1033

    
1034
        if( !isGood ) return false;
1035
        }
1036
      }
1037

    
1038
    return true;
1039
    }
1040

    
1041
///////////////////////////////////////////////////////////////////////////////////////////////////
1042

    
1043
  private boolean isSolved0()
1044
    {
1045
    if( mSolvedQuats[0][0]==0 ) return isSolvedCentersOnly();
1046

    
1047
    for( int[] solvedQuat : mSolvedQuats )
1048
      {
1049
      int numCubits = solvedQuat[0];
1050
      int firstCubit= solvedQuat[1];
1051
      int quat = mCubits[firstCubit].mQuatIndex;
1052

    
1053
      for( int cubit=2; cubit<=numCubits; cubit++ )
1054
        {
1055
        int c = solvedQuat[cubit];
1056
        if( quat != mCubits[c].mQuatIndex ) return false;
1057
        }
1058
      }
1059

    
1060
    int cubit= mSolvedQuats[0][1];
1061
    int quat0= mCubits[cubit].mQuatIndex;
1062
    int numGroups = mSolvedQuats.length;
1063

    
1064
    for(int group=1; group<numGroups; group++)
1065
      {
1066
      int firstCubit= mSolvedQuats[group][1];
1067
      int currQuat  = mCubits[firstCubit].mQuatIndex;
1068

    
1069
      if( quat0==currQuat ) continue;
1070

    
1071
      boolean isGood= false;
1072
      int numEntries= mSolvedQuats[group].length;
1073
      int numCubits = mSolvedQuats[group][0];
1074

    
1075
      for(int q=numCubits+1; q<numEntries; q++)
1076
        {
1077
        int quat = mSolvedQuats[group][q];
1078

    
1079
        if( quat0 == getMultQuat(currQuat,quat) )
1080
          {
1081
          isGood = true;
1082
          break;
1083
          }
1084
        }
1085

    
1086
      if( !isGood ) return false;
1087
      }
1088

    
1089
    return true;
1090
    }
1091

    
1092
///////////////////////////////////////////////////////////////////////////////////////////////////
1093

    
1094
  private int computeScramble(int quatNum, int centerNum)
1095
    {
1096
    float MAXDIFF = 0.01f;
1097
    float[] center= mOrigPos[centerNum];
1098
    Static4D sc = new Static4D(center[0], center[1], center[2], 1.0f);
1099
    Static4D result = QuatHelper.rotateVectorByQuat(sc,mObjectQuats[quatNum]);
1100

    
1101
    float x = result.get0();
1102
    float y = result.get1();
1103
    float z = result.get2();
1104

    
1105
    for(int c=0; c<mNumCubits; c++)
1106
      {
1107
      float[] cent = mOrigPos[c];
1108

    
1109
      float qx = cent[0] - x;
1110
      float qy = cent[1] - y;
1111
      float qz = cent[2] - z;
1112

    
1113
      if( qx>-MAXDIFF && qx<MAXDIFF &&
1114
          qy>-MAXDIFF && qy<MAXDIFF &&
1115
          qz>-MAXDIFF && qz<MAXDIFF  ) return c;
1116
      }
1117

    
1118
    return -1;
1119
    }
1120

    
1121
///////////////////////////////////////////////////////////////////////////////////////////////////
1122
// Dino4 uses this. It is solved if and only if groups of cubits
1123
// (0,3,7), (1,2,5), (4,8,9), (6,10,11)
1124
// or
1125
// (0,1,4), (2,3,6), (5,9,10), (7,8,11)
1126
// are all the same color.
1127

    
1128
  private boolean isSolved1()
1129
    {
1130
    if( mScramble==null )
1131
      {
1132
      mScramble = new int[mNumQuats][mNumCubits];
1133
      mColors   = new int[mNumCubits];
1134

    
1135
      for(int q=0; q<mNumQuats; q++)
1136
        for(int c=0; c<mNumCubits; c++) mScramble[q][c] = computeScramble(q,c);
1137
      }
1138

    
1139
    if( mFaceMap==null )
1140
      {
1141
      mFaceMap = new int[] { 4, 2, 2, 4, 0, 2, 1, 4, 0, 0, 1, 1 };
1142
      }
1143

    
1144
    for(int c=0; c<mNumCubits; c++)
1145
      {
1146
      int index = mScramble[mCubits[c].mQuatIndex][c];
1147
      mColors[index] = mFaceMap[c];
1148
      }
1149

    
1150
    if( mColors[0]==mColors[3] && mColors[0]==mColors[7] &&
1151
        mColors[1]==mColors[2] && mColors[1]==mColors[5] &&
1152
        mColors[4]==mColors[8] && mColors[4]==mColors[9]  ) return true;
1153

    
1154
    if( mColors[0]==mColors[1] && mColors[0]==mColors[4] &&
1155
        mColors[2]==mColors[3] && mColors[2]==mColors[6] &&
1156
        mColors[5]==mColors[9] && mColors[5]==mColors[10] ) return true;
1157

    
1158
    return false;
1159
    }
1160

    
1161
///////////////////////////////////////////////////////////////////////////////////////////////////
1162

    
1163
  int computeRow(float[] pos, int axisIndex, int cubitType, int puzzleFace)
1164
    {
1165
    int ret=0;
1166
    int len = pos.length / 3;
1167
    Static3D axis = mAxis[axisIndex];
1168
    float axisX = axis.get0();
1169
    float axisY = axis.get1();
1170
    float axisZ = axis.get2();
1171
    float casted, xoff=0, yoff=0, zoff=0;
1172

    
1173
    if( cubitType!=Cubit.TYPE_NORMAL )
1174
      {
1175
      xoff = mRowOffsets[puzzleFace][0];
1176
      yoff = mRowOffsets[puzzleFace][1];
1177
      zoff = mRowOffsets[puzzleFace][2];
1178
      }
1179

    
1180
    for(int i=0; i<len; i++)
1181
      {
1182
      casted = (pos[3*i]+xoff)*axisX + (pos[3*i+1]+yoff)*axisY + (pos[3*i+2]+zoff)*axisZ;
1183
      ret |= computeSingleRow(axisIndex,casted);
1184
      }
1185

    
1186
    return ret;
1187
    }
1188

    
1189
///////////////////////////////////////////////////////////////////////////////////////////////////
1190

    
1191
  private int computeSingleRow(int axisIndex,float casted)
1192
    {
1193
    int num = mNumCuts[axisIndex];
1194

    
1195
    for(int i=0; i<num; i++)
1196
      {
1197
      if( casted<mCuts[axisIndex][i] ) return (1<<i);
1198
      }
1199

    
1200
    return (1<<num);
1201
    }
1202

    
1203
///////////////////////////////////////////////////////////////////////////////////////////////////
1204

    
1205
  private boolean wasRotateApplied()
1206
    {
1207
    return mEffects.exists(mRotateEffect.getID());
1208
    }
1209

    
1210
///////////////////////////////////////////////////////////////////////////////////////////////////
1211

    
1212
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
1213
    {
1214
    return (mCubits[cubit].getRotRow(axis) & rowBitmap) != 0;
1215
    }
1216

    
1217
///////////////////////////////////////////////////////////////////////////////////////////////////
1218
// note the minus in front of the sin() - we rotate counterclockwise
1219
// when looking towards the direction where the axis increases in values.
1220

    
1221
  private Static4D makeQuaternion(float axisX, float axisY, float axisZ, int angleInDegrees)
1222
    {
1223
    while( angleInDegrees<0 ) angleInDegrees += 360;
1224
    angleInDegrees %= 360;
1225
    
1226
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
1227
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
1228

    
1229
    return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
1230
    }
1231

    
1232
///////////////////////////////////////////////////////////////////////////////////////////////////
1233

    
1234
  private synchronized void setupPosition(int[][] moves)
1235
    {
1236
    if( moves!=null )
1237
      {
1238
      Static4D quat;
1239
      int index, axisIndex, row, rowBitmap, basic, angle;
1240

    
1241
      for(int[] move: moves)
1242
        {
1243
        axisIndex= move[0];
1244
        rowBitmap= computeBitmapFromRow( move[1],axisIndex);
1245
        row      = computeRowFromBitmap( move[1] );
1246
        basic    = mBasicAngles[axisIndex][row];
1247
        angle    = move[2]*(360/basic);   // this assumes that all layers from
1248
                                          // the bitmap have the same BasicAngle.
1249
                                          // at the moment this is always true as
1250
                                          // there are no bandaged objects with
1251
                                          // different per-layer BasicAngles.
1252
        Static3D axis = mAxis[axisIndex];
1253
        float axisX = axis.get0();
1254
        float axisY = axis.get1();
1255
        float axisZ = axis.get2();
1256
        quat = makeQuaternion(axisX,axisY,axisZ,angle);
1257

    
1258
        for(int i=0; i<mNumCubits; i++)
1259
          {
1260
          mBelongs[i] = belongsToRotation(i,axisIndex,rowBitmap);
1261
          if( mBelongs[i] )
1262
            {
1263
            boolean result = mCubits[i].rotateCubit(quat);
1264
            if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,1);
1265
            }
1266
          }
1267

    
1268
        recomputeFaceOffsets();
1269

    
1270
        for(int i=0; i<mNumCubits; i++)
1271
          {
1272
          if( mBelongs[i] )
1273
            {
1274
            index = mCubits[i].postRotateCubit(quat);
1275
            setCubitQuat(i,mCubits[i].computeAssociation(),index);
1276
            }
1277
          else if( mCubits[i].getType()==Cubit.TYPE_FOLLOWER )
1278
            {
1279
            mCubits[i].computeRotationRow();
1280
            setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
1281
            }
1282
          }
1283
        }
1284
      }
1285
    }
1286

    
1287
///////////////////////////////////////////////////////////////////////////////////////////////////
1288

    
1289
  public int getScrambleType()
1290
    {
1291
    return 0;
1292
    }
1293

    
1294
///////////////////////////////////////////////////////////////////////////////////////////////////
1295

    
1296
  int computeBitmapFromRow(int rowBitmap, int axis)
1297
    {
1298
    if( mIsBandaged )
1299
      {
1300
      int bitmap, initBitmap=0;
1301

    
1302
      while( initBitmap!=rowBitmap )
1303
        {
1304
        initBitmap = rowBitmap;
1305

    
1306
        for(int cubit=0; cubit<mNumCubits; cubit++)
1307
          {
1308
          bitmap = mCubits[cubit].getRotRow(axis);
1309
          if( (rowBitmap & bitmap) != 0 ) rowBitmap |= bitmap;
1310
          }
1311
        }
1312
      }
1313

    
1314
    return rowBitmap;
1315
    }
1316

    
1317
///////////////////////////////////////////////////////////////////////////////////////////////////
1318

    
1319
  private int computeRowFromBitmap(int rowBitmap)
1320
    {
1321
    int index = 0;
1322

    
1323
    while(index<32)
1324
      {
1325
      if( (rowBitmap&0x1) != 0 ) return index;
1326
      rowBitmap>>=1;
1327
      index++;
1328
      }
1329
    return 0;
1330
    }
1331

    
1332
///////////////////////////////////////////////////////////////////////////////////////////////////
1333
// Clamp all rotated positions to one of those original ones to avoid accumulating errors.
1334
// Do so only if minimal Error is appropriately low (shape-shifting puzzles - Square-1)
1335

    
1336
  void clampPos(float[] pos, int offset)
1337
    {
1338
    float currError, minError = Float.MAX_VALUE;
1339
    int minErrorIndex1 = -1;
1340
    int minErrorIndex2 = -1;
1341

    
1342
    float x = pos[offset  ];
1343
    float y = pos[offset+1];
1344
    float z = pos[offset+2];
1345

    
1346
    float xo,yo,zo;
1347

    
1348
    for(int i=0; i<mNumCubits; i++)
1349
      {
1350
      int len = mOrigPos[i].length / 3;
1351

    
1352
      for(int j=0; j<len; j++)
1353
        {
1354
        xo = mOrigPos[i][3*j  ];
1355
        yo = mOrigPos[i][3*j+1];
1356
        zo = mOrigPos[i][3*j+2];
1357

    
1358
        currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
1359

    
1360
        if( currError<minError )
1361
          {
1362
          minError = currError;
1363
          minErrorIndex1 = i;
1364
          minErrorIndex2 = j;
1365
          }
1366
        }
1367
      }
1368

    
1369
    if( minError< 0.05f ) // TODO: 0.05 ?
1370
      {
1371
      pos[offset  ] = mOrigPos[minErrorIndex1][3*minErrorIndex2  ];
1372
      pos[offset+1] = mOrigPos[minErrorIndex1][3*minErrorIndex2+1];
1373
      pos[offset+2] = mOrigPos[minErrorIndex1][3*minErrorIndex2+2];
1374
      }
1375
    }
1376

    
1377
///////////////////////////////////////////////////////////////////////////////////////////////////
1378
// remember about the double cover or unit quaternions!
1379

    
1380
  int mulQuat(int q1, int q2)
1381
    {
1382
    Static4D result = QuatHelper.quatMultiply(mObjectQuats[q1],mObjectQuats[q2]);
1383

    
1384
    float rX = result.get0();
1385
    float rY = result.get1();
1386
    float rZ = result.get2();
1387
    float rW = result.get3();
1388

    
1389
    final float MAX_ERROR = 0.1f;
1390
    float dX,dY,dZ,dW;
1391

    
1392
    for(int i=0; i<mNumQuats; i++)
1393
      {
1394
      dX = mObjectQuats[i].get0() - rX;
1395
      dY = mObjectQuats[i].get1() - rY;
1396
      dZ = mObjectQuats[i].get2() - rZ;
1397
      dW = mObjectQuats[i].get3() - rW;
1398

    
1399
      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
1400
          dY<MAX_ERROR && dY>-MAX_ERROR &&
1401
          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
1402
          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
1403

    
1404
      dX = mObjectQuats[i].get0() + rX;
1405
      dY = mObjectQuats[i].get1() + rY;
1406
      dZ = mObjectQuats[i].get2() + rZ;
1407
      dW = mObjectQuats[i].get3() + rW;
1408

    
1409
      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
1410
          dY<MAX_ERROR && dY>-MAX_ERROR &&
1411
          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
1412
          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
1413
      }
1414

    
1415
    return -1;
1416
    }
1417

    
1418
///////////////////////////////////////////////////////////////////////////////////////////////////
1419

    
1420
  private float getAngle()
1421
    {
1422
    int pointNum = mRotationAngle.getNumPoints();
1423

    
1424
    if( pointNum>=1 )
1425
      {
1426
      return mRotationAngle.getPoint(pointNum-1).get0();
1427
      }
1428
    else
1429
      {
1430
      mInterface.reportProblem("points in RotationAngle: "+pointNum, false);
1431
      return 0;
1432
      }
1433
    }
1434

    
1435
///////////////////////////////////////////////////////////////////////////////////////////////////
1436

    
1437
  void setLibInterface(ObjectLibInterface inter)
1438
    {
1439
    mInterface = inter;
1440
    }
1441

    
1442
///////////////////////////////////////////////////////////////////////////////////////////////////
1443

    
1444
  void applyScrambles(int[][] moves)
1445
    {
1446
    setupPosition(moves);
1447
    }
1448

    
1449
///////////////////////////////////////////////////////////////////////////////////////////////////
1450

    
1451
  void initializeObject(int[][] moves)
1452
    {
1453
    solve();
1454
    setupPosition(moves);
1455
    }
1456

    
1457
///////////////////////////////////////////////////////////////////////////////////////////////////
1458

    
1459
  synchronized void removeRotationNow()
1460
    {
1461
    float angle = getAngle();
1462
    double nearestAngleInRadians = angle*Math.PI/180;
1463
    float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
1464
    float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
1465
    float axisX = mAxis[mCurrentRotAxis].get0();
1466
    float axisY = mAxis[mCurrentRotAxis].get1();
1467
    float axisZ = mAxis[mCurrentRotAxis].get2();
1468
    Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
1469

    
1470
    mRotationAngle.removeAll();
1471
    mRotationAngleStatic.set0(0);
1472

    
1473
    for(int i=0; i<mNumCubits; i++)
1474
      {
1475
      mBelongs[i] = belongsToRotation(i, mCurrentRotAxis,mRotRowBitmap);
1476
      if( mBelongs[i] )
1477
        {
1478
        boolean result = mCubits[i].rotateCubit(quat);
1479
        if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,2);
1480
        }
1481
      }
1482

    
1483
    recomputeFaceOffsets();
1484

    
1485
    for(int i=0; i<mNumCubits; i++)
1486
      {
1487
      if( mBelongs[i] )
1488
        {
1489
        int index = mCubits[i].postRotateCubit(quat);
1490
        setCubitQuat(i,mCubits[i].computeAssociation(),index);
1491
        }
1492
      else if( mCubits[i].getType()==Cubit.TYPE_FOLLOWER )
1493
        {
1494
        mCubits[i].computeRotationRow();
1495
        setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
1496
        }
1497
      }
1498
    }
1499

    
1500
///////////////////////////////////////////////////////////////////////////////////////////////////
1501

    
1502
  private void recomputeFaceOffsets()
1503
    {
1504
    for(int i=0; i<mNumPuzzleFaces; i++)
1505
      {
1506
      mRowOffsets[i][0] =0;
1507
      mRowOffsets[i][1] =0;
1508
      mRowOffsets[i][2] =0;
1509
      }
1510

    
1511
    for(int i=0; i<mNumCubits; i++)
1512
      if( mCubits[i].getType()==Cubit.TYPE_DECIDER )
1513
        {
1514
        float[] offset = mCubits[i].getOffset();
1515
        int face = mCubits[i].getPuzzleFace();
1516
        mRowOffsets[face][0] = offset[0];
1517
        mRowOffsets[face][1] = offset[1];
1518
        mRowOffsets[face][2] = offset[2];
1519
        }
1520
    }
1521

    
1522
///////////////////////////////////////////////////////////////////////////////////////////////////
1523

    
1524
  long finishRotationNow(EffectListener listener, int nearestAngleInDegrees)
1525
    {
1526
    if( wasRotateApplied() )
1527
      {
1528
      float angle = getAngle();
1529
      mRotationAngleStatic.set0(angle);
1530
      mRotationAngleFinal.set0(nearestAngleInDegrees);
1531
      mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
1532

    
1533
      mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
1534
      mRotationAngle.resetToBeginning();
1535
      mRotationAngle.removeAll();
1536
      mRotationAngle.add(mRotationAngleStatic);
1537
      mRotationAngle.add(mRotationAngleMiddle);
1538
      mRotationAngle.add(mRotationAngleFinal);
1539
      mRotateEffect.notifyWhenFinished(listener);
1540

    
1541
      return mRotateEffect.getID();
1542
      }
1543

    
1544
    return 0;
1545
    }
1546

    
1547
///////////////////////////////////////////////////////////////////////////////////////////////////
1548

    
1549
  synchronized long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
1550
    {
1551
    if( wasRotateApplied() )
1552
      {
1553
      mCurrentRotAxis = axis;
1554
      mRotRowBitmap= computeBitmapFromRow( rowBitmap,axis );
1555

    
1556
      mRotationAngleStatic.set0(0.0f);
1557
      mRotationAxis.set( mAxis[axis] );
1558
      mRotationAngle.setDuration(durationMillis);
1559
      mRotationAngle.resetToBeginning();
1560
      mRotationAngle.add(new Static1D(0));
1561
      mRotationAngle.add(new Static1D(angle));
1562
      mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1563
      mRotateEffect.notifyWhenFinished(listener);
1564

    
1565
      return mRotateEffect.getID();
1566
      }
1567

    
1568
    return 0;
1569
    }
1570

    
1571
///////////////////////////////////////////////////////////////////////////////////////////////////
1572

    
1573
  void continueRotation(float angleInDegrees)
1574
    {
1575
    mRotationAngleStatic.set0(angleInDegrees);
1576
    }
1577

    
1578
///////////////////////////////////////////////////////////////////////////////////////////////////
1579

    
1580
  synchronized void beginNewRotation(int axis, int row )
1581
    {
1582
    if( axis<0 || axis>=mNumAxis )
1583
      {
1584
      android.util.Log.e("object", "invalid rotation axis: "+axis);
1585
      return;
1586
      }
1587
    if( row<0 || row>=mNumLayers[axis] )
1588
      {
1589
      android.util.Log.e("object", "invalid rotation row: "+row);
1590
      return;
1591
      }
1592

    
1593
    mCurrentRotAxis = axis;
1594
    mRotRowBitmap= computeBitmapFromRow( (1<<row),axis );
1595
    mRotationAngleStatic.set0(0.0f);
1596
    mRotationAxis.set( mAxis[axis] );
1597
    mRotationAngle.add(mRotationAngleStatic);
1598
    mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1599
    }
1600

    
1601
///////////////////////////////////////////////////////////////////////////////////////////////////
1602

    
1603
  void setTextureMap(int cubit, int face, int color)
1604
    {
1605
    int variant  = getCubitVariant(cubit,mNumLayers);
1606
    int shape    = getVariantStickerShape(variant,face);
1607
    int texIndex = color<0 || shape<0 ? mNumTextures-mNumOverrides : shape*mNumFaceColors + color;
1608
    int row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1609
    int col      = texIndex%mNumTexCols;
1610

    
1611
    final float ratioW = 1.0f/mNumTexCols;
1612
    final float ratioH = 1.0f/mNumTexRows;
1613
    final Static4D[] maps = new Static4D[1];
1614
    maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1615
    mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1616
    }
1617

    
1618
///////////////////////////////////////////////////////////////////////////////////////////////////
1619

    
1620
  private int getCubitFaceColor(int cubit, int face)
1621
    {
1622
    int puzzleFace = getCubitFaceMap(cubit,face);
1623
    if( puzzleFace>=0 ) puzzleFace %= mNumFaceColors;
1624
    return puzzleFace;
1625
    }
1626

    
1627
///////////////////////////////////////////////////////////////////////////////////////////////////
1628

    
1629
  public int getCubitFaceMap(int cubit, int face)
1630
    {
1631
    int numFaces = mCubitFaceColors[cubit].length;
1632
    int puzzleFace = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
1633
    return puzzleFace<0 ? -1 : puzzleFace;
1634
    }
1635

    
1636
///////////////////////////////////////////////////////////////////////////////////////////////////
1637

    
1638
  void resetAllTextureMaps()
1639
    {
1640
    final float ratioW = 1.0f/mNumTexCols;
1641
    final float ratioH = 1.0f/mNumTexRows;
1642
    int cubColor, stiShape, texIndex, variant, row, col;
1643

    
1644
    for(int cubit=0; cubit<mNumCubits; cubit++)
1645
      {
1646
      final Static4D[] maps = new Static4D[mNumCubitFaces];
1647
      variant = getCubitVariant(cubit,mNumLayers);
1648

    
1649
      for(int face=0; face<mNumCubitFaces; face++)
1650
        {
1651
        cubColor = getCubitFaceColor(cubit,face);
1652
        stiShape = getVariantStickerShape(variant,face);
1653
        texIndex = cubColor<0 || stiShape<0 ? mNumTextures-mNumOverrides : stiShape*mNumFaceColors + cubColor;
1654
        row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1655
        col      = texIndex%mNumTexCols;
1656

    
1657
        maps[face] = new Static4D( col*ratioW, row*ratioH, ratioW, ratioH);
1658
        }
1659

    
1660
      mMesh.setTextureMap(maps,mNumCubitFaces*cubit);
1661
      }
1662

    
1663
    overrideCubitFaceColor();
1664
    }
1665

    
1666
///////////////////////////////////////////////////////////////////////////////////////////////////
1667

    
1668
  private void overrideCubitFaceColor()
1669
    {
1670
    final float ratioW = 1.0f/mNumTexCols;
1671
    final float ratioH = 1.0f/mNumTexRows;
1672

    
1673
    for(int i=0; i<mNumOverrides; i++)
1674
      {
1675
      int[] cubitFaces = mStickerOverrides[i].getCubitFaces();
1676
      int length = cubitFaces.length/2;
1677

    
1678
      for(int j=0; j<length; j++)
1679
        {
1680
        final Static4D[] maps = new Static4D[1];
1681
        int color = mNumTextures-mNumOverrides+1+i;
1682
        int row   = (mNumTexRows-1) - color/mNumTexCols;
1683
        int col   = color%mNumTexCols;
1684
        int cubit = cubitFaces[2*j];
1685
        int face  = cubitFaces[2*j+1];
1686
        maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1687
        mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1688
        }
1689
      }
1690
    }
1691

    
1692
///////////////////////////////////////////////////////////////////////////////////////////////////
1693

    
1694
  void releaseResources()
1695
    {
1696
    mTexture.markForDeletion();
1697
    mMesh.markForDeletion();
1698
    mEffects.markForDeletion();
1699

    
1700
    for(int j=0; j<mNumCubits; j++)
1701
      {
1702
      mCubits[j].releaseResources();
1703
      }
1704
    }
1705

    
1706
///////////////////////////////////////////////////////////////////////////////////////////////////
1707

    
1708
  private void setCubitQuat(int cubit, int andAssociation, int equAssociation)
1709
    {
1710
    if( !mIsInMixupMode )
1711
      {
1712
      mMesh.setEffectAssociation(cubit,andAssociation,equAssociation);
1713
      }
1714
    else
1715
      {
1716
      mMesh.setEffectAssociation(cubit,andAssociation,cubit);
1717
      Static4D tmp = mObjectQuats[equAssociation];
1718
      mMixupModeQuats[cubit].set(tmp);
1719
      }
1720
    }
1721

    
1722
///////////////////////////////////////////////////////////////////////////////////////////////////
1723

    
1724
  synchronized void restorePreferences(SharedPreferences preferences)
1725
    {
1726
    boolean error = false;
1727
    String key = getShortName();
1728

    
1729
    for(int i=0; i<mNumCubits; i++)
1730
      {
1731
      mQuatDebug[i] = mCubits[i].restorePreferences(key,preferences);
1732

    
1733
      if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1734
        {
1735
        boolean result = mCubits[i].rotateCubit(mObjectQuats[mQuatDebug[i]]);
1736
        if( !result ) debugQuat(mObjectQuats[mQuatDebug[i]],i,0,0,0,mQuatDebug[i],3);
1737
        }
1738
      else { error = true; break; }
1739
      }
1740

    
1741
    if( !error )
1742
      {
1743
      recomputeFaceOffsets();
1744

    
1745
      for(int i=0; i<mNumCubits; i++)
1746
        {
1747
        if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1748
          {
1749
          mCubits[i].computeRotationRow();
1750
          setCubitQuat(i,mCubits[i].computeAssociation(),mQuatDebug[i]);
1751
          }
1752
        else { error = true; break; }
1753
        }
1754
      }
1755

    
1756
    if( error )
1757
      {
1758
      for(int i=0; i<mNumCubits; i++)
1759
        {
1760
        mCubits[i].solve();
1761
        setCubitQuat(i,mCubits[i].computeAssociation(),0);
1762
        }
1763
      }
1764
    }
1765

    
1766
///////////////////////////////////////////////////////////////////////////////////////////////////
1767

    
1768
  void savePreferences(SharedPreferences.Editor editor)
1769
    {
1770
    String key = getShortName();
1771
    for(int i=0; i<mNumCubits; i++) mCubits[i].savePreferences(key,editor);
1772
    }
1773

    
1774
///////////////////////////////////////////////////////////////////////////////////////////////////
1775

    
1776
  public void removePreferences(SharedPreferences.Editor editor)
1777
    {
1778
    String key = getShortName();
1779
    for(int i=0; i<mNumCubits; i++) mCubits[i].removePreferences(key,editor);
1780
    }
1781

    
1782
///////////////////////////////////////////////////////////////////////////////////////////////////
1783

    
1784
  private float computeRadiusCorrection(float[] sticker, int curr, int len)
1785
    {
1786
    final float A = 0.8f;  // 0<A<1
1787

    
1788
    int prev = curr>0 ? curr-1 : len-1;
1789
    int next = curr<len-1 ? curr+1 : 0;
1790

    
1791
    float v1x = sticker[2*prev  ]-sticker[2*curr  ];
1792
    float v1y = sticker[2*prev+1]-sticker[2*curr+1];
1793
    float v2x = sticker[2*next  ]-sticker[2*curr  ];
1794
    float v2y = sticker[2*next+1]-sticker[2*curr+1];
1795

    
1796
    float len1= v1x*v1x+v1y*v1y;
1797
    float len2= v2x*v2x+v2y*v2y;
1798

    
1799
    float cos = (v1x*v2x+v1y*v2y) / ( (float)Math.sqrt(len1*len2) );
1800

    
1801
    return 1-A*cos;
1802
    }
1803

    
1804
///////////////////////////////////////////////////////////////////////////////////////////////////
1805
// Radius of the sphere circumscribed on the puzzle. Needed for pillowing.
1806
//
1807
// This won't work correctly for pillowing off-center puzzles (e.g. mirrors) - for those we'd need
1808
// to introduce the concept of a 'sink center' as well.
1809
//
1810
// public because needed in TouchControlShapemod
1811

    
1812
  public float getCircumscribedRadius()
1813
    {
1814
    switch(mNumPuzzleFaces)
1815
      {
1816
      case  4: return (SQ6/4)*mSize;
1817
      case  6: return (SQ3/2)*mSize;
1818
      case  8: return (SQ2/2)*mSize;
1819
      case 12: return (SQ3/2)*((SQ5+1)/2)*mSize;
1820
      case 16: return 0.50f*mSize;
1821
      }
1822

    
1823
    return 0.0f;
1824
    }
1825

    
1826
///////////////////////////////////////////////////////////////////////////////////////////////////
1827

    
1828
  public ObjectSticker retSticker(int sticker)
1829
    {
1830
    if( mStickers==null )
1831
      {
1832
      float rad = getStickerRadius();
1833
      float str = getStickerStroke();
1834
      float[][] angles = getStickerAngles();
1835
      int numStickers = mStickerCoords.length;
1836
      mStickers = new ObjectSticker[numStickers];
1837

    
1838
      for(int s=0; s<numStickers; s++)
1839
        {
1840
        float scale = mStickerScales.length>s ? mStickerScales[s] : 1.0f;
1841
        float radius = rad / scale;
1842
        float stroke = str / scale;
1843
        int len = mStickerCoords[s].length/2;
1844
        float[] radii = new float[len];
1845
        for(int r=0; r<len; r++) radii[r] = radius*computeRadiusCorrection(mStickerCoords[s],r,len);
1846
        mStickers[s] = new ObjectSticker(mStickerCoords[s],angles==null ? null : angles[s],radii,stroke);
1847
        }
1848
      }
1849

    
1850
    return mStickers[sticker];
1851
    }
1852

    
1853
///////////////////////////////////////////////////////////////////////////////////////////////////
1854
// some objects (currently Kilominx,Ivy,Rex) might want to change the stickers.
1855

    
1856
  public void adjustStickerCoords()
1857
    {
1858

    
1859
    }
1860

    
1861
///////////////////////////////////////////////////////////////////////////////////////////////////
1862

    
1863
  public Static4D[] getQuats()
1864
    {
1865
    if( mObjectQuats==null )
1866
      {
1867
      mObjectQuats = QuatGroupGenerator.computeGroup(mAxis,mBasicAngles);
1868
      }
1869

    
1870
    return mObjectQuats;
1871
    }
1872

    
1873
///////////////////////////////////////////////////////////////////////////////////////////////////
1874

    
1875
  public int[][] getVariantFaceIsOuter()
1876
    {
1877
    return mVariantFaceIsOuter;
1878
    }
1879

    
1880
///////////////////////////////////////////////////////////////////////////////////////////////////
1881

    
1882
  public int getInternalColor()
1883
    {
1884
    return COLOR_INTERNAL;
1885
    }
1886

    
1887
///////////////////////////////////////////////////////////////////////////////////////////////////
1888
// the getFaceColors + final INTERNAL_COLOR in a grid (so that we do not exceed the maximum texture size)
1889

    
1890
  private void createTexture()
1891
    {
1892
    Paint paint = new Paint();
1893
    mBitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_4444);
1894
    Canvas canvas = new Canvas(mBitmap);
1895

    
1896
    paint.setAntiAlias(true);
1897
    paint.setTextAlign(Paint.Align.CENTER);
1898
    paint.setStyle(Paint.Style.FILL);
1899
    paint.setColor(getInternalColor());
1900
    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
1901

    
1902
    int texture = 0;
1903
    FactorySticker factory = FactorySticker.getInstance();
1904

    
1905
    for(int row=0; row<mNumTexRows; row++)
1906
      for(int col=0; col<mNumTexCols; col++)
1907
        {
1908
        if( texture<mNumTextures-mNumOverrides )
1909
          {
1910
          ObjectSticker sticker = retSticker(texture/mNumFaceColors);
1911
          int color = getColor(texture%mNumFaceColors);
1912
          factory.drawRoundedPolygon(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color, sticker);
1913
          }
1914
        else if( texture>mNumTextures-mNumOverrides && texture<=mNumTextures )
1915
          {
1916
          int color = mStickerOverrides[mNumTextures-texture].getColor();
1917
          factory.drawSolidColor(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color);
1918
          }
1919

    
1920
        texture++;
1921
        }
1922
    }
1923

    
1924
///////////////////////////////////////////////////////////////////////////////////////////////////
1925

    
1926
  void setTexture()
1927
    {
1928
    if( mBitmap==null ) createTexture();
1929

    
1930
    if( !mTexture.setTextureAlreadyInverted(mBitmap) )
1931
      {
1932
      int max = DistortedLibrary.getMaxTextureSize();
1933
      mInterface.reportProblem("failed to set texture of size "+mBitmap.getWidth()+"x"+mBitmap.getHeight()+" max is "+max, true);
1934
      }
1935
    }
1936

    
1937
///////////////////////////////////////////////////////////////////////////////////////////////////
1938

    
1939
  void setObjectRatioNow(float sc, int nodeSize)
1940
    {
1941
    mObjectScreenRatio = sc;
1942
    float scale = mObjectScreenRatio*mInitScreenRatio*nodeSize/mSize;
1943
    mObjectScale.set(scale,scale,scale);
1944

    
1945
    if( mTouchControl ==null ) mTouchControl = getTouchControl();
1946
    mTouchControl.setObjectRatio(mObjectScreenRatio*mInitScreenRatio);
1947
    }
1948

    
1949
///////////////////////////////////////////////////////////////////////////////////////////////////
1950

    
1951
  void setObjectRatio(float sizeChange, int nodeSize)
1952
    {
1953
    mObjectScreenRatio *= (1.0f+sizeChange)/2;
1954

    
1955
    if( mObjectScreenRatio>MAX_SIZE_CHANGE) mObjectScreenRatio = MAX_SIZE_CHANGE;
1956
    if( mObjectScreenRatio<MIN_SIZE_CHANGE) mObjectScreenRatio = MIN_SIZE_CHANGE;
1957

    
1958
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1959
    }
1960

    
1961
///////////////////////////////////////////////////////////////////////////////////////////////////
1962

    
1963
  void setNodeSize(int nodeSize)
1964
    {
1965
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1966
    }
1967

    
1968
///////////////////////////////////////////////////////////////////////////////////////////////////
1969

    
1970
  public float getRatio()
1971
    {
1972
    return mObjectScreenRatio;
1973
    }
1974

    
1975
///////////////////////////////////////////////////////////////////////////////////////////////////
1976

    
1977
  public float getObjectRatio()
1978
    {
1979
    return mObjectScreenRatio*mInitScreenRatio;
1980
    }
1981

    
1982
///////////////////////////////////////////////////////////////////////////////////////////////////
1983

    
1984
  boolean isSolved()
1985
    {
1986
    if( mSolvedFunctionIndex==0 ) return isSolved0();
1987
    if( mSolvedFunctionIndex==1 ) return isSolved1();
1988

    
1989
    return false;
1990
    }
1991

    
1992
///////////////////////////////////////////////////////////////////////////////////////////////////
1993

    
1994
  int computeNearestAngle(int basicAngle, float angle, float speed)
1995
    {
1996
    int nearestAngle = 360/basicAngle;
1997
    int tmp = (int)((angle+nearestAngle/2)/nearestAngle);
1998
    if( angle< -(nearestAngle*0.5) ) tmp-=1;
1999

    
2000
    if( tmp!=0 ) return nearestAngle*tmp;
2001

    
2002
    return speed> 1.2f ? nearestAngle*(angle>0 ? 1:-1) : 0;
2003
    }
2004

    
2005
///////////////////////////////////////////////////////////////////////////////////////////////////
2006
// INTERNAL API - those are called from 'effects' package
2007
///////////////////////////////////////////////////////////////////////////////////////////////////
2008

    
2009
  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
2010
    {
2011
    mScrambler.randomizeNewScramble(scramble,rnd,curr,total, getSignature() );
2012
    }
2013

    
2014
///////////////////////////////////////////////////////////////////////////////////////////////////
2015

    
2016
  public Static4D getRotationQuat()
2017
    {
2018
    return mQuat;
2019
    }
2020

    
2021
///////////////////////////////////////////////////////////////////////////////////////////////////
2022

    
2023
  public float getSize()
2024
    {
2025
    return mSize;
2026
    }
2027

    
2028
///////////////////////////////////////////////////////////////////////////////////////////////////
2029

    
2030
  public void applyEffect(Effect effect, int position)
2031
    {
2032
    mEffects.apply(effect, position);
2033
    }
2034

    
2035
///////////////////////////////////////////////////////////////////////////////////////////////////
2036

    
2037
  public void removeEffect(long effectID)
2038
    {
2039
    mEffects.abortById(effectID);
2040
    }
2041

    
2042
///////////////////////////////////////////////////////////////////////////////////////////////////
2043

    
2044
  public MeshBase getObjectMesh()
2045
    {
2046
    return mMesh;
2047
    }
2048

    
2049
///////////////////////////////////////////////////////////////////////////////////////////////////
2050

    
2051
  public DistortedEffects getObjectEffects()
2052
    {
2053
    return mEffects;
2054
    }
2055

    
2056
///////////////////////////////////////////////////////////////////////////////////////////////////
2057

    
2058
  public int getCubitType(int cubit)
2059
    {
2060
    return mCubits[cubit].getType();
2061
    }
2062

    
2063
///////////////////////////////////////////////////////////////////////////////////////////////////
2064

    
2065
  public float[] getCubitOffset(int cubit)
2066
    {
2067
    return mCubits[cubit].getOffset();
2068
    }
2069

    
2070
///////////////////////////////////////////////////////////////////////////////////////////////////
2071

    
2072
  public ObjectStickerOverride[] getStickerOverrides()
2073
    {
2074
    return null;
2075
    }
2076

    
2077
///////////////////////////////////////////////////////////////////////////////////////////////////
2078

    
2079
  public boolean getError()
2080
    {
2081
    return mError;
2082
    }
2083

    
2084
///////////////////////////////////////////////////////////////////////////////////////////////////
2085

    
2086
  public String getErrorString()
2087
    {
2088
    return mErrorString;
2089
    }
2090

    
2091
///////////////////////////////////////////////////////////////////////////////////////////////////
2092
// PUBLIC API
2093
///////////////////////////////////////////////////////////////////////////////////////////////////
2094

    
2095
  public int getCubitFaceColorIndex(int cubit, int face)
2096
    {
2097
    Static4D texMap = mMesh.getTextureMap(mNumFaceColors *cubit + face);
2098

    
2099
    int x = (int)(texMap.get0()/texMap.get2());
2100
    int y = (int)(texMap.get1()/texMap.get3());
2101

    
2102
    return (mNumTexRows-1-y)*NUM_STICKERS_IN_ROW + x;
2103
    }
2104

    
2105
///////////////////////////////////////////////////////////////////////////////////////////////////
2106

    
2107
  public int[] getNumLayers()
2108
    {
2109
    return mNumLayers;
2110
    }
2111

    
2112
///////////////////////////////////////////////////////////////////////////////////////////////////
2113

    
2114
  public synchronized void solve()
2115
    {
2116
    for(int i=0; i<mNumCubits; i++)
2117
      {
2118
      mCubits[i].solve();
2119
      }
2120

    
2121
    recomputeFaceOffsets();
2122

    
2123
    for(int i=0; i<mNumCubits; i++)
2124
      {
2125
      mCubits[i].computeRotationRow();
2126
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
2127
      }
2128
    }
2129

    
2130
///////////////////////////////////////////////////////////////////////////////////////////////////
2131

    
2132
  public int getCubitQuatIndex(int cubit)
2133
    {
2134
    return (cubit>=0 && cubit<mNumCubits) ? mCubits[cubit].mQuatIndex : 0;
2135
    }
2136

    
2137
///////////////////////////////////////////////////////////////////////////////////////////////////
2138

    
2139
  public int getCubitRotRow(int cubit, int axis)
2140
    {
2141
    return mCubits[cubit].getRotRow(axis);
2142
    }
2143

    
2144
///////////////////////////////////////////////////////////////////////////////////////////////////
2145

    
2146
  public Bitmap getStickerBitmap()
2147
    {
2148
    return mBitmap;
2149
    }
2150

    
2151
///////////////////////////////////////////////////////////////////////////////////////////////////
2152

    
2153
  public DistortedNode getNode()
2154
    {
2155
    return mNode;
2156
    }
2157

    
2158
///////////////////////////////////////////////////////////////////////////////////////////////////
2159

    
2160
  public int getNumStickerTypes()
2161
    {
2162
    return mNumStickerTypes;
2163
    }
2164

    
2165
///////////////////////////////////////////////////////////////////////////////////////////////////
2166

    
2167
  public String reportState()
2168
    {
2169
    StringBuilder builder = new StringBuilder();
2170

    
2171
    for(int i=0; i<mNumCubits; i++ )
2172
      {
2173
      if( i>0 ) builder.append('.');
2174
      builder.append(mCubits[i].mQuatIndex);
2175
      }
2176

    
2177
    return builder.toString();
2178
    }
2179

    
2180
///////////////////////////////////////////////////////////////////////////////////////////////////
2181
// this is here only so it can be overridden in TwistyJSON so that we can get this from JSON.
2182

    
2183
  public int getNumCubitFaces()
2184
    {
2185
    return 0;
2186
    }
2187

    
2188
///////////////////////////////////////////////////////////////////////////////////////////////////
2189
// 1.0 - i.e. no pillowing - by default.
2190
// The coeff is really param of the 'sink' vertex effect - if it is not equal to 1.0, we apply the
2191
// sink effect [centered at (0,0,0)] to the whole mesh as the last step of composing it.
2192

    
2193
  public float getPillowCoeff()
2194
    {
2195
    return 1.0f;
2196
    }
2197

    
2198
///////////////////////////////////////////////////////////////////////////////////////////////////
2199

    
2200
  public TouchControl getTouchControl()
2201
    {
2202
    if( mTouchControl==null )
2203
      {
2204
      switch(getTouchControlType())
2205
        {
2206
        case TC_TETRAHEDRON      : mTouchControl = new TouchControlTetrahedron(this);
2207
                                   break;
2208
        case TC_HEXAHEDRON       : mTouchControl = new TouchControlHexahedron(this);
2209
                                   break;
2210
        case TC_OCTAHEDRON       : mTouchControl = new TouchControlOctahedron(this);
2211
                                   break;
2212
        case TC_DODECAHEDRON     : mTouchControl = new TouchControlDodecahedron(this);
2213
                                   break;
2214
        case TC_ICOSAHEDRON      : mTouchControl = new TouchControlIcosahedron(this);
2215
                                   break;
2216
        case TC_CUBOID           : int[] numLayers = getNumLayers();
2217
                                   mTouchControl = new TouchControlCuboids(this,getDist3D(numLayers));
2218
                                   break;
2219
        case TC_BALL             : mTouchControl = new TouchControlBall(this);
2220
                                   break;
2221
        case TC_CHANGING_MIRROR  : mTouchControl = new TouchControlMirror(this);
2222
                                   break;
2223
        case TC_CHANGING_SQUARE  : mTouchControl = new TouchControlSquare(this);
2224
                                   break;
2225
        case TC_CHANGING_SHAPEMOD: mTouchControl = new TouchControlShapemod(this);
2226
                                   break;
2227
        }
2228
      }
2229
    return mTouchControl;
2230
    }
2231

    
2232
///////////////////////////////////////////////////////////////////////////////////////////////////
2233

    
2234
  protected void setReader(JsonReader reader)
2235
    {
2236
    // empty
2237
    }
2238

    
2239
///////////////////////////////////////////////////////////////////////////////////////////////////
2240
  // for JSON only
2241
  public abstract int getTouchControlType();
2242
  public abstract int getTouchControlSplit();
2243
  public abstract boolean[][] getLayerRotatable(int[] numLayers);
2244
  public abstract int[][][] getEnabled();
2245
  public abstract float[] getDist3D(int[] numLayers);
2246
  public abstract Static3D[] getFaceAxis();
2247
  public abstract int[][] getScrambleEdges();
2248
  public abstract float[][] getCuts(int[] numLayers);
2249
  public abstract float getStickerRadius();
2250
  public abstract float getStickerStroke();
2251
  public abstract float[][] getStickerAngles();
2252
  public abstract int getCubitVariant(int cubit, int[] numLayers);
2253
  public abstract ObjectShape getObjectShape(int variant);
2254
  public abstract ObjectFaceShape getObjectFaceShape(int variant);
2255
  public abstract ObjectVertexEffects getVertexEffects(int variant);
2256
  public abstract int getNumCubitVariants(int[] numLayers);
2257
  public abstract float[][] getCubitPositions(int[] numLayers);
2258
  public abstract Static4D getCubitQuats(int cubit, int[] numLayers);
2259
  public abstract int getNumFaceColors();
2260
  public abstract float getScreenRatio();
2261
  public abstract int getColor(int face);
2262
  public abstract String getShortName();
2263
  public abstract ObjectSignature getSignature();
2264

    
2265
  // not only for JSON
2266
  public abstract Static3D[] getRotationAxis();
2267
  public abstract int[][] getBasicAngles();
2268
  public abstract int getNumFaces();
2269
  public abstract String getObjectName();
2270
  public abstract String getInventor();
2271
  public abstract int getYearOfInvention();
2272
  public abstract int getComplexity();
2273
  public abstract int getFOV();
2274
  public abstract String[][] getTutorials();
2275
  }
(8-8/9)