Project

General

Profile

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

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

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.ScrambleState;
54
import org.distorted.objectlib.scrambling.ObjectScrambler;
55
import org.distorted.objectlib.json.JsonReader;
56
import org.distorted.objectlib.touchcontrol.*;
57

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

    
60
///////////////////////////////////////////////////////////////////////////////////////////////////
61

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

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

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

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

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

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

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

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

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

    
156
  //////////////////// SOLVED1 ////////////////////////
157

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

    
162
///////////////////////////////////////////////////////////////////////////////////////////////////
163

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

    
185
///////////////////////////////////////////////////////////////////////////////////////////////////
186

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

    
197
///////////////////////////////////////////////////////////////////////////////////////////////////
198

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

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

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

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

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

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

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

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

    
245
print_table("EDGES", edges);
246
print_table("ALGOS", algorithms);
247

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

    
250
    boolean bandaged=false;
251

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

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

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

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

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

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

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

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

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

    
295
///////////////////////////////////////////////////////////////////////////////////////////////////
296

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

    
301
    int len = table.length;
302

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

    
312
///////////////////////////////////////////////////////////////////////////////////////////////////
313

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

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

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

    
346
///////////////////////////////////////////////////////////////////////////////////////////////////
347

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

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

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

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

    
369
///////////////////////////////////////////////////////////////////////////////////////////////////
370

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

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

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

    
394
///////////////////////////////////////////////////////////////////////////////////////////////////
395

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

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

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

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

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

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

    
426
///////////////////////////////////////////////////////////////////////////////////////////////////
427

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

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

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

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

    
457
      mMesh = new MeshJoined(cubitMesh);
458

    
459
      float pillowCoeff = getPillowCoeff();
460

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

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

    
478
///////////////////////////////////////////////////////////////////////////////////////////////////
479

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

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

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

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

    
499
    return mesh;
500
    }
501

    
502
///////////////////////////////////////////////////////////////////////////////////////////////////
503

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

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

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

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

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

    
534
    setTexture();
535
    }
536

    
537
///////////////////////////////////////////////////////////////////////////////////////////////////
538

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

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

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

    
555
    return -1;
556
    }
557

    
558
///////////////////////////////////////////////////////////////////////////////////////////////////
559

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

    
565
///////////////////////////////////////////////////////////////////////////////////////////////////
566

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

    
572
///////////////////////////////////////////////////////////////////////////////////////////////////
573

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

    
579
///////////////////////////////////////////////////////////////////////////////////////////////////
580

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

    
586
///////////////////////////////////////////////////////////////////////////////////////////////////
587

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

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

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

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

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

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

    
622
    return ret;
623
    }
624

    
625
///////////////////////////////////////////////////////////////////////////////////////////////////
626

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

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

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

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

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

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

    
662
    return nonBlackIndex;
663
    }
664

    
665
///////////////////////////////////////////////////////////////////////////////////////////////////
666

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

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

    
679
    return false;
680
    }
681

    
682
///////////////////////////////////////////////////////////////////////////////////////////////////
683

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

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

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

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

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

    
705
    return true;
706
    }
707

    
708
///////////////////////////////////////////////////////////////////////////////////////////////////
709

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

    
717
    return ret;
718
    }
719

    
720
///////////////////////////////////////////////////////////////////////////////////////////////////
721

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

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

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

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

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

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

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

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

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

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

    
777
///////////////////////////////////////////////////////////////////////////////////////////////////
778

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

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

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

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

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

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

    
817
    return null;
818
    }
819

    
820
///////////////////////////////////////////////////////////////////////////////////////////////////
821

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

    
827
///////////////////////////////////////////////////////////////////////////////////////////////////
828

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

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

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

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

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

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

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

    
890
    return null;
891
    }
892

    
893
///////////////////////////////////////////////////////////////////////////////////////////////////
894

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

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

    
914
      return -2;
915
      }
916

    
917
    return -1;
918
    }
919

    
920
///////////////////////////////////////////////////////////////////////////////////////////////////
921

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

    
927
///////////////////////////////////////////////////////////////////////////////////////////////////
928

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

    
936
///////////////////////////////////////////////////////////////////////////////////////////////////
937

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

    
943
///////////////////////////////////////////////////////////////////////////////////////////////////
944

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

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

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

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

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

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

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

    
1000
///////////////////////////////////////////////////////////////////////////////////////////////////
1001

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

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

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

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

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

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

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

    
1039
    return true;
1040
    }
1041

    
1042
///////////////////////////////////////////////////////////////////////////////////////////////////
1043

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

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

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

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

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

    
1070
      if( quat0==currQuat ) continue;
1071

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

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

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

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

    
1090
    return true;
1091
    }
1092

    
1093
///////////////////////////////////////////////////////////////////////////////////////////////////
1094

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

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

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

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

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

    
1119
    return -1;
1120
    }
1121

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

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

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

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

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

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

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

    
1159
    return false;
1160
    }
1161

    
1162
///////////////////////////////////////////////////////////////////////////////////////////////////
1163

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

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

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

    
1187
    return ret;
1188
    }
1189

    
1190
///////////////////////////////////////////////////////////////////////////////////////////////////
1191

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

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

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

    
1204
///////////////////////////////////////////////////////////////////////////////////////////////////
1205

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

    
1211
///////////////////////////////////////////////////////////////////////////////////////////////////
1212

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

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

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

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

    
1233
///////////////////////////////////////////////////////////////////////////////////////////////////
1234

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

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

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

    
1269
        recomputeFaceOffsets();
1270

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

    
1288
///////////////////////////////////////////////////////////////////////////////////////////////////
1289

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

    
1295
///////////////////////////////////////////////////////////////////////////////////////////////////
1296

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

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

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

    
1315
    return rowBitmap;
1316
    }
1317

    
1318
///////////////////////////////////////////////////////////////////////////////////////////////////
1319

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

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

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

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

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

    
1347
    float xo,yo,zo;
1348

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1416
    return -1;
1417
    }
1418

    
1419
///////////////////////////////////////////////////////////////////////////////////////////////////
1420

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

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

    
1436
///////////////////////////////////////////////////////////////////////////////////////////////////
1437

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

    
1443
///////////////////////////////////////////////////////////////////////////////////////////////////
1444

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

    
1450
///////////////////////////////////////////////////////////////////////////////////////////////////
1451

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

    
1458
///////////////////////////////////////////////////////////////////////////////////////////////////
1459

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

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

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

    
1484
    recomputeFaceOffsets();
1485

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

    
1501
///////////////////////////////////////////////////////////////////////////////////////////////////
1502

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

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

    
1523
///////////////////////////////////////////////////////////////////////////////////////////////////
1524

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

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

    
1542
      return mRotateEffect.getID();
1543
      }
1544

    
1545
    return 0;
1546
    }
1547

    
1548
///////////////////////////////////////////////////////////////////////////////////////////////////
1549

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

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

    
1566
      return mRotateEffect.getID();
1567
      }
1568

    
1569
    return 0;
1570
    }
1571

    
1572
///////////////////////////////////////////////////////////////////////////////////////////////////
1573

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

    
1579
///////////////////////////////////////////////////////////////////////////////////////////////////
1580

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

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

    
1602
///////////////////////////////////////////////////////////////////////////////////////////////////
1603

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

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

    
1619
///////////////////////////////////////////////////////////////////////////////////////////////////
1620

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

    
1628
///////////////////////////////////////////////////////////////////////////////////////////////////
1629

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

    
1637
///////////////////////////////////////////////////////////////////////////////////////////////////
1638

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

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

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

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

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

    
1664
    overrideCubitFaceColor();
1665
    }
1666

    
1667
///////////////////////////////////////////////////////////////////////////////////////////////////
1668

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

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

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

    
1693
///////////////////////////////////////////////////////////////////////////////////////////////////
1694

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

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

    
1707
///////////////////////////////////////////////////////////////////////////////////////////////////
1708

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

    
1723
///////////////////////////////////////////////////////////////////////////////////////////////////
1724

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

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

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

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

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

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

    
1767
///////////////////////////////////////////////////////////////////////////////////////////////////
1768

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

    
1775
///////////////////////////////////////////////////////////////////////////////////////////////////
1776

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

    
1783
///////////////////////////////////////////////////////////////////////////////////////////////////
1784

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

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

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

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

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

    
1802
    return 1-A*cos;
1803
    }
1804

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

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

    
1824
    return 0.0f;
1825
    }
1826

    
1827
///////////////////////////////////////////////////////////////////////////////////////////////////
1828

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

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

    
1851
    return mStickers[sticker];
1852
    }
1853

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

    
1857
  public void adjustStickerCoords()
1858
    {
1859

    
1860
    }
1861

    
1862
///////////////////////////////////////////////////////////////////////////////////////////////////
1863

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

    
1871
    return mObjectQuats;
1872
    }
1873

    
1874
///////////////////////////////////////////////////////////////////////////////////////////////////
1875

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

    
1881
///////////////////////////////////////////////////////////////////////////////////////////////////
1882

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

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

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

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

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

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

    
1921
        texture++;
1922
        }
1923
    }
1924

    
1925
///////////////////////////////////////////////////////////////////////////////////////////////////
1926

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

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

    
1938
///////////////////////////////////////////////////////////////////////////////////////////////////
1939

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

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

    
1950
///////////////////////////////////////////////////////////////////////////////////////////////////
1951

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

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

    
1959
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1960
    }
1961

    
1962
///////////////////////////////////////////////////////////////////////////////////////////////////
1963

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

    
1969
///////////////////////////////////////////////////////////////////////////////////////////////////
1970

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

    
1976
///////////////////////////////////////////////////////////////////////////////////////////////////
1977

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

    
1983
///////////////////////////////////////////////////////////////////////////////////////////////////
1984

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

    
1990
    return false;
1991
    }
1992

    
1993
///////////////////////////////////////////////////////////////////////////////////////////////////
1994

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

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

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

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

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

    
2015
///////////////////////////////////////////////////////////////////////////////////////////////////
2016

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

    
2022
///////////////////////////////////////////////////////////////////////////////////////////////////
2023

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

    
2029
///////////////////////////////////////////////////////////////////////////////////////////////////
2030

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

    
2036
///////////////////////////////////////////////////////////////////////////////////////////////////
2037

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

    
2043
///////////////////////////////////////////////////////////////////////////////////////////////////
2044

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

    
2050
///////////////////////////////////////////////////////////////////////////////////////////////////
2051

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

    
2057
///////////////////////////////////////////////////////////////////////////////////////////////////
2058

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

    
2064
///////////////////////////////////////////////////////////////////////////////////////////////////
2065

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

    
2071
///////////////////////////////////////////////////////////////////////////////////////////////////
2072

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

    
2078
///////////////////////////////////////////////////////////////////////////////////////////////////
2079

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

    
2085
///////////////////////////////////////////////////////////////////////////////////////////////////
2086

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

    
2092
///////////////////////////////////////////////////////////////////////////////////////////////////
2093
// PUBLIC API
2094
///////////////////////////////////////////////////////////////////////////////////////////////////
2095

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

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

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

    
2106
///////////////////////////////////////////////////////////////////////////////////////////////////
2107

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

    
2113
///////////////////////////////////////////////////////////////////////////////////////////////////
2114

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

    
2122
    recomputeFaceOffsets();
2123

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

    
2131
///////////////////////////////////////////////////////////////////////////////////////////////////
2132

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

    
2138
///////////////////////////////////////////////////////////////////////////////////////////////////
2139

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

    
2145
///////////////////////////////////////////////////////////////////////////////////////////////////
2146

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

    
2152
///////////////////////////////////////////////////////////////////////////////////////////////////
2153

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

    
2159
///////////////////////////////////////////////////////////////////////////////////////////////////
2160

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

    
2166
///////////////////////////////////////////////////////////////////////////////////////////////////
2167

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

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

    
2178
    return builder.toString();
2179
    }
2180

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

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

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

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

    
2199
///////////////////////////////////////////////////////////////////////////////////////////////////
2200

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

    
2233
///////////////////////////////////////////////////////////////////////////////////////////////////
2234

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

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

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