Project

General

Profile

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

distorted-objectlib / src / main / java / org / distorted / objectlib / main / TwistyObject.java @ 6f5eb9b3

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.graphics.Bitmap;
18
import android.graphics.Canvas;
19
import android.graphics.Paint;
20

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

    
40
import org.distorted.objectlib.helpers.FactoryCubit;
41
import org.distorted.objectlib.helpers.FactorySticker;
42
import org.distorted.objectlib.helpers.ObjectFaceShape;
43
import org.distorted.objectlib.helpers.ObjectLibInterface;
44
import org.distorted.objectlib.helpers.ObjectShape;
45
import org.distorted.objectlib.metadata.Metadata;
46
import org.distorted.objectlib.signature.ObjectSignature;
47
import org.distorted.objectlib.helpers.ObjectSticker;
48
import org.distorted.objectlib.helpers.ObjectStickerOverride;
49
import org.distorted.objectlib.helpers.ObjectVertexEffects;
50
import org.distorted.objectlib.helpers.OperatingSystemInterface;
51
import org.distorted.objectlib.helpers.QuatGroupGenerator;
52
import org.distorted.objectlib.scrambling.ObjectScrambler;
53
import org.distorted.objectlib.json.JsonReader;
54
import org.distorted.objectlib.scrambling.ScrambleEdgeGenerator;
55
import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
56
import org.distorted.objectlib.tablebases.TablebasesAbstract;
57
import org.distorted.objectlib.touchcontrol.*;
58

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

    
61
///////////////////////////////////////////////////////////////////////////////////////////////////
62

    
63
public abstract class TwistyObject
64
  {
65
  public static final int MODE_ICON = 0;
66
  public static final int MODE_NORM = 1;
67

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

    
79
  public static final int COLOR_RED_TET  = 0xffff1111;
80
  public static final int COLOR_BLUE_TET = 0xff4444ff;
81

    
82
  private static final int DEFAULT_TEXTURE_HEIGHT = 256;
83
  private static final int DEFAULT_TEXTURE_ROWS   = 8;
84

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

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

    
93
  private static final Static3D CENTER = new Static3D(0,0,0);
94

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

    
98
  private int[][] mStickerVariants;
99
  private float[] mStickerScales;
100
  private TwistyObjectCubit[] mCubits;
101
  private TwistyObjectSolved mSolved;
102
  private MeshBase[] mMeshes;
103
  private int mNumCubits, mNumQuats, mNumFaceColors, mNumTextures, mNumOverrides;
104
  private int mNumCubitFaces, mNumStickerTypes;
105
  private Static3D[] mAxis;
106
  private float[][] mCuts;
107
  private int[][] mMinimalCubiesInRow;
108
  private int[] mNumCuts;
109
  private float[][] mOrigPos;
110
  private Static4D[] mOrigQuat;
111
  private Static4D[] mMixupModeQuats;
112
  private boolean mIsInMixupMode;
113
  private Static4D mQuat;
114
  private int[] mNumLayers;
115
  private float mSize;
116
  private DistortedEffects mEffects;
117
  private Static3D mObjectScale;
118
  private int[] mQuatDebug;
119
  private DistortedTexture mTexture;
120
  private float mInitScreenRatio;
121
  private boolean mIsBandaged;
122
  private float mObjectScreenRatio;
123
  private int mNumTexRows, mNumTexCols, mTexHeight;
124
  private MeshBase mMesh;
125
  private ObjectScrambler mScrambler;
126
  private TouchControl mTouchControl;
127
  private DistortedNode mNode;
128
  private ObjectLibInterface mInterface;
129
  private Bitmap mBitmap;
130
  private ObjectSticker[] mStickers;
131
  private ObjectShape[] mShapes;
132
  private int mNumCubitVariants;
133
  private int[][] mCubitFaceColors;
134
  private int[][] mVariantFaceIsOuter;
135
  private int[][] mBasicAngles;
136
  private int mIconMode;
137
  private Metadata mMetadata;
138
  private float[][] mRowOffsets;
139
  private boolean[] mBelongs;
140
  private float[] mTmp;
141
  private int mNumPuzzleFaces;
142
  private ObjectStickerOverride[] mStickerOverrides;
143
  private boolean mError;
144
  private String mErrorString;
145
  private int mMaxNumLayers;
146
  private int mNumAxis;
147
  private boolean mThereAreDeciders;
148
  private TwistyLayerRotations mRotation;
149
  private int[] mColorTable;
150
  private float mTextureBorderMultiplier, mTextureCornerMultiplier;
151

    
152
///////////////////////////////////////////////////////////////////////////////////////////////////
153

    
154
  TwistyObject(int iconMode, Static4D quat, Static3D move, float scale, InitAssets asset)
155
    {
156
    try
157
      {
158
      InputStream jsonStream = asset!=null ? asset.getJsonStream(): null;
159
      JsonReader reader = new JsonReader();
160
      reader.parseJsonFile(jsonStream);
161
      setReader(reader);
162
      mNumLayers = reader.getNumLayers();
163
      mSize      = reader.getSize();
164
      mMetadata  = null;
165
      initialize(iconMode,quat,move,scale,asset,true);
166
      if( asset!=null ) asset.close();
167
      mError = false;
168
      mErrorString=null;
169
      }
170
    catch(Exception ex)
171
      {
172
      mError = true;
173
      mErrorString = ex.getMessage();
174
      }
175
    }
176

    
177
///////////////////////////////////////////////////////////////////////////////////////////////////
178

    
179
  public TwistyObject(int iconMode, float size, Static4D quat, Static3D move, float scale, Metadata meta, InitAssets asset)
180
    {
181
    mNumLayers = meta.getNumLayers();
182
    mSize      = size;
183
    mMetadata  = meta;
184
    initialize(iconMode,quat,move,scale,asset,false);
185
    if( asset!=null ) asset.close();
186
    mError = false;
187
    mErrorString = null;
188
    }
189

    
190
///////////////////////////////////////////////////////////////////////////////////////////////////
191

    
192
  private void debugQuat(Static4D quat, int cubitIndex, float axisX, float axisY, float axisZ, float angle, int place)
193
    {
194
    float[] tracking = mCubits[cubitIndex].getTrackingPoint();
195

    
196
    if( tracking!=null )
197
      {
198
      String problem = (getShortName()+" "+cubitIndex+" "+quat.get0()+" "+quat.get1()+" "+quat.get2()+" "+quat.get3());
199
      problem += (" "+angle+" "+place+" "+tracking[0]+" "+tracking[1]+" "+tracking[2]);
200
      problem += (" "+axisX+" "+axisY+" "+axisZ);
201

    
202
      mInterface.reportProblem(problem, true);
203
      }
204
    }
205

    
206
///////////////////////////////////////////////////////////////////////////////////////////////////
207

    
208
  private void initialize(int iconMode, Static4D quat, Static3D move, float scale, InitAssets asset, boolean fromJSON)
209
    {
210
    mIconMode = iconMode;
211
    mQuat = quat;
212
    mAxis = getRotationAxis();
213
    mInitScreenRatio = getScreenRatio();
214
    mBasicAngles = getBasicAngles();
215
    mObjectQuats = getQuats();
216
    mNumQuats = mObjectQuats.length;
217
    mOrigPos = getCubitPositions(mNumLayers);
218
    mNumPuzzleFaces = getNumFaces();
219
    mRowOffsets = new float[mNumPuzzleFaces][3];
220
    mTmp = new float[4];
221
    mNumAxis = mAxis.length;
222
    mCuts = getCuts(mNumLayers);
223
    mNumCuts = new int[mNumAxis];
224
    mMaxNumLayers = -1;
225
    for(int i=0; i<mNumAxis; i++)
226
      {
227
      if( mMaxNumLayers<mNumLayers[i] ) mMaxNumLayers = mNumLayers[i];
228
      mNumCuts[i] = (mCuts==null || mCuts[i]==null ? 0 : mCuts[i].length);
229
      }
230

    
231
    recreateFaceColors();
232
    mTextureBorderMultiplier = 1.0f;
233
    mTextureCornerMultiplier = 1.0f;
234

    
235
    mMinimalCubiesInRow = getMinimalCubiesInRow();
236
    mNumCubits = mOrigPos.length;
237

    
238
    mBelongs = new boolean[mNumCubits];
239

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

    
244
    OperatingSystemInterface os = asset==null ? null : asset.getOS();
245
    TablebasesAbstract tablebase = os!=null ? getTablebase(os) : null;
246
    mScrambler = new ObjectScrambler(scramblingType,mNumAxis,mNumLayers,algorithms,edges,tablebase);
247

    
248
    boolean bandaged=false;
249

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

    
261
    mObjectScale = new Static3D(scale,scale,scale);
262
    setObjectRatioNow(scale,720);
263

    
264
    mEffects = new DistortedEffects();
265
    createQuaternionEffects();
266

    
267
    mRotation = new TwistyLayerRotations(this,mAxis,mNumLayers,getGhostAngle(),mEffects);
268

    
269
    MatrixEffectScale scaleEffect = new MatrixEffectScale(mObjectScale);
270
    MatrixEffectQuaternion quatEffect = new MatrixEffectQuaternion(mQuat, CENTER);
271
    MatrixEffectMove moveEffect = new MatrixEffectMove(move);
272

    
273
    InputStream meshStream = asset!=null ? asset.getMeshStream(): null;
274
    boolean fromDMESH = (meshStream!=null);
275
    getQuatsAndShapes(fromDMESH,fromJSON);
276
    createMeshAndCubits(meshStream,fromDMESH);
277
    setUpTextures(fromDMESH,fromJSON);
278

    
279
    int index = getSolvedFunctionIndex();
280
    mSolved = new TwistyObjectSolved(this,mOrigPos,index);
281
    mSolved.setupSolvedQuats(getSolvedQuats());
282

    
283
    mEffects.apply(quatEffect);
284
    mEffects.apply(scaleEffect);
285
    mEffects.apply(moveEffect);
286

    
287
    mNode = new DistortedNode(mTexture,mEffects,mMesh);
288
    }
289

    
290
///////////////////////////////////////////////////////////////////////////////////////////////////
291

    
292
  public void recreateFaceColors()
293
    {
294
    int[] tmp = getColorTable();
295
    mNumFaceColors = tmp.length;
296
    mColorTable = new int[mNumFaceColors];
297
    for(int i=0; i<mNumFaceColors; i++) mColorTable[i] = tmp[i];
298
    }
299

    
300
///////////////////////////////////////////////////////////////////////////////////////////////////
301
// in degrees so that everything can be treated modulo 360
302

    
303
  public int getGhostAngle()
304
    {
305
    return 0;
306
    }
307

    
308
///////////////////////////////////////////////////////////////////////////////////////////////////
309

    
310
  private TablebasesAbstract getTablebase(OperatingSystemInterface os)
311
    {
312
    String shortName = getShortName();
313
    return ImplementedTablebasesList.createPacked(os,shortName);
314
    }
315

    
316
///////////////////////////////////////////////////////////////////////////////////////////////////
317

    
318
  private void createQuaternionEffects()
319
    {
320
    if( mNumQuats<=ObjectControl.MAX_QUATS )
321
      {
322
      mIsInMixupMode = false;
323

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

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

    
350
///////////////////////////////////////////////////////////////////////////////////////////////////
351

    
352
  private Static3D getPos(float[] origPos)
353
    {
354
    int len = origPos.length/3;
355
    float sumX = 0.0f;
356
    float sumY = 0.0f;
357
    float sumZ = 0.0f;
358

    
359
    for(int i=0; i<len; i++)
360
      {
361
      sumX += origPos[3*i  ];
362
      sumY += origPos[3*i+1];
363
      sumZ += origPos[3*i+2];
364
      }
365

    
366
    sumX /= len;
367
    sumY /= len;
368
    sumZ /= len;
369

    
370
    return new Static3D(sumX,sumY,sumZ);
371
    }
372

    
373
///////////////////////////////////////////////////////////////////////////////////////////////////
374

    
375
  private void createOuterFaces()
376
    {
377
    for(int v=0; v<mNumCubitVariants; v++)
378
      {
379
      int numFaces = mShapes[v].getNumFaces();
380
      mVariantFaceIsOuter[v] = new int[numFaces];
381
      }
382

    
383
    for( int cubit=0; cubit<mNumCubits; cubit++)
384
      {
385
      int variant = getCubitVariant(cubit,mNumLayers);
386
      int numFaces = mShapes[variant].getNumFaces();
387

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

    
396
///////////////////////////////////////////////////////////////////////////////////////////////////
397

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

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

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

    
412
      mOrigQuat = new Static4D[mNumCubits];
413
      for(int i=0; i<mNumCubits; i++) mOrigQuat[i] = getCubitQuats(i,mNumLayers);
414

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

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

    
428
///////////////////////////////////////////////////////////////////////////////////////////////////
429

    
430
  private void createMeshAndCubits(InputStream stream, boolean fromDMESH)
431
    {
432
    mCubits = new TwistyObjectCubit[mNumCubits];
433

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

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

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

    
459
      mMesh = new MeshJoined(cubitMesh);
460

    
461
      float pillowCoeff = getPillowCoeff();
462

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

    
473
    for(int i=0; i<mNumCubits; i++)
474
      {
475
      mCubits[i] = new TwistyObjectCubit(this,mOrigPos[i],mNumAxis,mMaxNumLayers,i);
476
      setCubitQuat(i,0);
477

    
478
      if( !mThereAreDeciders && mCubits[i].getType()==TwistyObjectCubit.TYPE_DECIDER )
479
        {
480
        mThereAreDeciders = true;
481
        }
482
      }
483
    }
484

    
485
///////////////////////////////////////////////////////////////////////////////////////////////////
486

    
487
  private MeshBase createCubitMesh(int cubit, int[] numLayers, int numComponents)
488
    {
489
    int variant = getCubitVariant(cubit,numLayers);
490

    
491
    if( mMeshes==null ) mMeshes = new MeshBase[mNumCubitVariants];
492

    
493
    if( mMeshes[variant]==null )
494
      {
495
      ObjectFaceShape faceShape = getObjectFaceShape(variant);
496
      ObjectVertexEffects effects = getVertexEffects(variant);
497
      FactoryCubit factory = FactoryCubit.getInstance();
498
      factory.createNewFaceTransform(mShapes[variant],mVariantFaceIsOuter[variant]);
499
      mMeshes[variant] = factory.createRoundedSolid(mShapes[variant],faceShape,effects,numComponents);
500
      }
501

    
502
    MeshBase mesh = mMeshes[variant].copy(true);
503
    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( mOrigQuat[cubit], CENTER );
504
    mesh.apply(quat,0xffffffff,0);
505

    
506
    return mesh;
507
    }
508

    
509
///////////////////////////////////////////////////////////////////////////////////////////////////
510

    
511
  private void figureOutBitmapDimensions(int numTextures)
512
    {
513
    int maxSize = DistortedLibrary.getMaxTextureSize();
514

    
515
    mTexHeight  = DEFAULT_TEXTURE_HEIGHT;
516

    
517
    while(true)
518
      {
519
      mNumTexCols = DEFAULT_TEXTURE_ROWS;
520
      mNumTexRows = numTextures/mNumTexCols + 1;
521

    
522
      if( mNumTexRows*mTexHeight <= maxSize &&
523
          mNumTexCols*mTexHeight <= maxSize  ) break;
524

    
525
      mTexHeight/=2;
526
      }
527
    }
528

    
529
///////////////////////////////////////////////////////////////////////////////////////////////////
530

    
531
  private void setUpTextures(boolean fromDMESH, boolean fromJSON)
532
    {
533
    mTexture = new DistortedTexture();
534

    
535
    if( fromJSON )
536
      {
537
      mNumStickerTypes = getNumStickerTypes();
538
      mNumCubitFaces = getNumCubitFaces();
539
      }
540
    else
541
      {
542
      FactoryCubit factory = FactoryCubit.getInstance();
543
      mStickerCoords   = factory.getStickerCoords();
544
      mStickerVariants = factory.getStickerVariants();
545
      mStickerScales   = factory.getStickerScales();
546
      adjustStickerCoords();
547
      mNumStickerTypes = (mStickerCoords==null ? 0 : mStickerCoords.length);
548
      }
549

    
550
    mStickerOverrides = getStickerOverrides();
551
    mNumOverrides = mStickerOverrides==null ? 0 : mStickerOverrides.length;
552

    
553
    mNumTextures= mNumFaceColors*mNumStickerTypes + mNumOverrides;
554
    figureOutBitmapDimensions(mNumTextures);
555
    if( mNumTexCols*mNumTexRows < mNumTextures+1 ) mNumTexRows++;
556

    
557
    if( !fromDMESH || shouldResetTextureMaps() ) resetAllTextureMaps();
558
    else overrideCubitFaceColor();
559

    
560
    setTexture();
561
    }
562

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

    
565
  public Metadata getMetadata()
566
    {
567
    return mMetadata;
568
    }
569

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

    
572
  public boolean isInIconMode()
573
    {
574
    return mIconMode==MODE_ICON;
575
    }
576

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

    
579
  public int getVariantStickerShape(int variant, int face)
580
    {
581
    if( variant <mStickerVariants.length )
582
      {
583
      int[] var = mStickerVariants[variant];
584
      return face>=var.length ? -1 : var[face];
585
      }
586
    return -1;
587
    }
588

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

    
591
  public boolean shouldResetTextureMaps()
592
    {
593
    return false;
594
    }
595

    
596
///////////////////////////////////////////////////////////////////////////////////////////////////
597

    
598
  public int[][] getScrambleAlgorithms()
599
    {
600
    return ScrambleEdgeGenerator.getScramblingAlgorithms(mBasicAngles);
601
    }
602

    
603
///////////////////////////////////////////////////////////////////////////////////////////////////
604

    
605
  private boolean sticksOut(Static3D[] faceAxis, float[] dist, float x, float y, float z )
606
    {
607
    final float MAXERR = 0.05f;
608
    int numAxis = dist.length;
609

    
610
    for(int i=0; i<numAxis; i++)
611
      {
612
      Static3D ax = faceAxis[i];
613
      float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
614
      if( len>mSize*dist[i]+MAXERR ) return true;
615
      }
616

    
617
    return false;
618
    }
619

    
620
///////////////////////////////////////////////////////////////////////////////////////////////////
621

    
622
  private boolean doesNotStickOut(int variant, float px, float py, float pz, float[] tmp, Static4D quat)
623
    {
624
    float[][] vertices = mShapes[variant].getVertices();
625
    Static3D[] axis = getFaceAxis();
626
    float[] dist3D = getDist3D(mNumLayers);
627

    
628
    for( float[] vertex : vertices)
629
      {
630
      float x = vertex[0];
631
      float y = vertex[1];
632
      float z = vertex[2];
633

    
634
      QuatHelper.rotateVectorByQuat(tmp, x, y, z, 1, quat);
635

    
636
      float mx = tmp[0] + px;
637
      float my = tmp[1] + py;
638
      float mz = tmp[2] + pz;
639

    
640
      if( sticksOut(axis, dist3D, mx,my,mz) ) return false;
641
      }
642

    
643
    return true;
644
    }
645

    
646
///////////////////////////////////////////////////////////////////////////////////////////////////
647

    
648
  private float computeAvg(float[] pos, int offset)
649
    {
650
    int len = pos.length/3;
651
    float ret=0.0f;
652
    for(int i=0; i<len; i++) ret += pos[3*i+offset];
653
    ret /= len;
654

    
655
    return ret;
656
    }
657

    
658
///////////////////////////////////////////////////////////////////////////////////////////////////
659

    
660
  protected void displayCubitQuats()
661
    {
662
    StringBuilder builder = new StringBuilder();
663
    float[] tmp = new float[4];
664
    float ERR = 0.01f;
665

    
666
    for(int cubit=0; cubit<mNumCubits; cubit++)
667
      {
668
      builder.append(cubit);
669
      builder.append(" : ");
670

    
671
      int refCubit,variant = getCubitVariant(cubit,mNumLayers);
672

    
673
      for(refCubit=0; refCubit<mNumCubits; refCubit++)
674
        {
675
        if( getCubitVariant(refCubit,mNumLayers)==variant ) break;
676
        }
677

    
678
      float[] curpos = mOrigPos[cubit];
679
      float[] refpos = mOrigPos[refCubit];
680
      float refX = computeAvg(refpos,0);
681
      float refY = computeAvg(refpos,1);
682
      float refZ = computeAvg(refpos,2);
683
      float curX = computeAvg(curpos,0);
684
      float curY = computeAvg(curpos,1);
685
      float curZ = computeAvg(curpos,2);
686

    
687
      for(int quat=0; quat<mNumQuats; quat++)
688
        {
689
        QuatHelper.rotateVectorByQuat(tmp,refX,refY,refZ,0,mObjectQuats[quat]);
690

    
691
        float dx = tmp[0]-curX;
692
        float dy = tmp[1]-curY;
693
        float dz = tmp[2]-curZ;
694

    
695
        if( dx>-ERR && dx<ERR && dy>-ERR && dy<ERR && dz>-ERR && dz<ERR )
696
          {
697
          if( doesNotStickOut(variant,curX,curY,curZ,tmp,mObjectQuats[quat]) )
698
            {
699
            builder.append(quat);
700
            builder.append(',');
701
            }
702
          else
703
            {
704
            android.util.Log.e("D", "cubit: "+cubit+" quat: "+quat+" : center correct, but shape sticks out");
705
            }
706
          }
707
        }
708

    
709
      builder.append('\n');
710
      }
711

    
712
    android.util.Log.e("D", "cubitQuats: \n"+builder );
713
    }
714

    
715
///////////////////////////////////////////////////////////////////////////////////////////////////
716

    
717
  public int getCubitRotationType(int cubit)
718
    {
719
    return TwistyObjectCubit.TYPE_NORMAL;
720
    }
721

    
722
///////////////////////////////////////////////////////////////////////////////////////////////////
723

    
724
  float[] getTrackingPoint(int cubitIndex, int cubitType)
725
    {
726
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
727
      {
728
      int variant = getCubitVariant(cubitIndex,mNumLayers);
729

    
730
      // object must have been created from JSON
731
      if( mVariantFaceIsOuter==null || mVariantFaceIsOuter[variant]==null )
732
        {
733
        mVariantFaceIsOuter = getVariantFaceIsOuter();
734
        }
735
      if( mShapes==null )
736
        {
737
        mShapes = new ObjectShape[mNumCubitVariants];
738
        }
739
      if( mShapes[variant]==null )
740
        {
741
        mShapes[variant] = getObjectShape(variant);
742
        }
743
      if( mOrigQuat==null )
744
        {
745
        mOrigQuat = new Static4D[mNumCubits];
746
        }
747
      if( mOrigQuat[cubitIndex]==null )
748
        {
749
        mOrigQuat[cubitIndex] = getCubitQuats(cubitIndex,mNumLayers);
750
        }
751

    
752
      int outer=-1, faces = mShapes[variant].getNumFaces();
753

    
754
      for(int i=0; i<faces; i++)
755
        {
756
        if( mVariantFaceIsOuter[variant][i]==1 )
757
          {
758
          outer=i;
759
          break;
760
          }
761
        }
762

    
763
      if( outer>=0 )
764
        {
765
        float[] v = mShapes[variant].getFirstVertexInFace(outer);
766
        float[] ret = new float[3];
767
        float[] curpos = mOrigPos[cubitIndex];
768
        Static4D quat = mOrigQuat[cubitIndex];
769
        QuatHelper.rotateVectorByQuat(mTmp, v[0], v[1], v[2], 1, quat);
770

    
771
        ret[0] = mTmp[0]+computeAvg(curpos,0);
772
        ret[1] = mTmp[1]+computeAvg(curpos,1);
773
        ret[2] = mTmp[2]+computeAvg(curpos,2);
774

    
775
        return ret;
776
        }
777
      else
778
        {
779
        android.util.Log.e("D", "Error in getTrackingPoint: no outer face??");
780
        }
781
      }
782

    
783
    return null;
784
    }
785

    
786
///////////////////////////////////////////////////////////////////////////////////////////////////
787

    
788
  public int computeCurrentPuzzleFace(int type, float[] vertex)
789
    {
790
    if( type!=TwistyObjectCubit.TYPE_NORMAL )
791
      {
792
      Static3D[] axis = getFaceAxis();
793
      float[] dist3D = getDist3D(mNumLayers);
794
      final float MAXERR = 0.98f;
795
      int numAxis = axis.length;
796
      float x = vertex[0];
797
      float y = vertex[1];
798
      float z = vertex[2];
799

    
800
      for(int i=0; i<numAxis; i++)
801
        {
802
        Static3D ax = axis[i];
803
        float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
804
        if( len>mSize*dist3D[i]*MAXERR ) return i;
805
        }
806

    
807
      return -2;
808
      }
809

    
810
    return -1;
811
    }
812

    
813
///////////////////////////////////////////////////////////////////////////////////////////////////
814

    
815
  public float[] getCubitRowOffset(int cubitIndex)
816
    {
817
    return null;
818
    }
819

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

    
822
  void setRotationRowOffset(int puzzleFace, float[] offset)
823
    {
824
    mRowOffsets[puzzleFace][0] = offset[0];
825
    mRowOffsets[puzzleFace][1] = offset[1];
826
    mRowOffsets[puzzleFace][2] = offset[2];
827
    }
828

    
829
///////////////////////////////////////////////////////////////////////////////////////////////////
830

    
831
  int getNumAxis()
832
    {
833
    return mNumAxis;
834
    }
835

    
836
///////////////////////////////////////////////////////////////////////////////////////////////////
837

    
838
  public int[][] getSolvedQuats()
839
    {
840
    return null;
841
    }
842

    
843
///////////////////////////////////////////////////////////////////////////////////////////////////
844

    
845
  protected int[][] getOldSolvedQuats()
846
    {
847
    return mSolved.getSolvedQuats(mNumLayers,mNumCubitFaces,mCubitFaceColors);
848
    }
849

    
850
///////////////////////////////////////////////////////////////////////////////////////////////////
851
// 0: old, groups-of-quaternions based function.
852
// Still used by: Masterball, Mirror Sqewb, Mirror Jing, Mirror Pyraminx & the Penrose Cubes.
853
//
854
// 1: specific function only for the Dino4.
855
//
856
// 2: the new default; automatic way based on automatic detection of monochromatic puzzle surfaces.
857

    
858
  public int getSolvedFunctionIndex()
859
    {
860
    return 2;
861
    }
862

    
863
///////////////////////////////////////////////////////////////////////////////////////////////////
864

    
865
  int computeRow(float[] pos, int axisIndex, int cubitType, int puzzleFace)
866
    {
867
    int ret=0;
868
    int len = pos.length / 3;
869
    Static3D axis = mAxis[axisIndex];
870
    float axisX = axis.get0();
871
    float axisY = axis.get1();
872
    float axisZ = axis.get2();
873
    float casted, xoff=0, yoff=0, zoff=0;
874

    
875
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
876
      {
877
      xoff = mRowOffsets[puzzleFace][0];
878
      yoff = mRowOffsets[puzzleFace][1];
879
      zoff = mRowOffsets[puzzleFace][2];
880
      }
881

    
882
    for(int i=0; i<len; i++)
883
      {
884
      casted = (pos[3*i]+xoff)*axisX + (pos[3*i+1]+yoff)*axisY + (pos[3*i+2]+zoff)*axisZ;
885
      ret |= computeSingleRow(axisIndex,casted);
886
      }
887

    
888
    return ret;
889
    }
890

    
891
///////////////////////////////////////////////////////////////////////////////////////////////////
892

    
893
  private int computeSingleRow(int axisIndex,float casted)
894
    {
895
    int num = mNumCuts[axisIndex];
896
    float[] cuts = mCuts[axisIndex];
897

    
898
    for(int i=0; i<num; i++)
899
      {
900
      if( casted<cuts[i] ) return (1<<i);
901
      }
902

    
903
    return (1<<num);
904
    }
905

    
906
///////////////////////////////////////////////////////////////////////////////////////////////////
907

    
908
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
909
    {
910
    return (mCubits[cubit].getRotRow(axis) & rowBitmap) != 0;
911
    }
912

    
913
///////////////////////////////////////////////////////////////////////////////////////////////////
914
// note the minus in front of the sin() - we rotate counterclockwise
915
// when looking towards the direction where the axis increases in values.
916

    
917
  private Static4D makeQuaternion(float axisX, float axisY, float axisZ, int angleInDegrees)
918
    {
919
    while( angleInDegrees<0 ) angleInDegrees += 360;
920
    angleInDegrees %= 360;
921
    
922
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
923
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
924

    
925
    return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
926
    }
927

    
928
///////////////////////////////////////////////////////////////////////////////////////////////////
929

    
930
  void rotateAllCubits(int axisIndex, int rowBitmap, int angle)
931
    {
932
    Static3D axis = mAxis[axisIndex];
933
    float axisX = axis.get0();
934
    float axisY = axis.get1();
935
    float axisZ = axis.get2();
936
    Static4D quat = makeQuaternion(axisX,axisY,axisZ,angle);
937

    
938
    for(int i=0; i<mNumCubits; i++)
939
      {
940
      mBelongs[i] = belongsToRotation(i,axisIndex,rowBitmap);
941
      if( mBelongs[i] )
942
        {
943
        boolean result = mCubits[i].rotateCubit(quat,false);
944
        if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,1);
945
        }
946
      }
947

    
948
    recomputeFaceOffsets();
949

    
950
    for(int i=0; i<mNumCubits; i++)
951
      {
952
      if( mBelongs[i] )
953
        {
954
        int index = mCubits[i].postRotateCubit(quat);
955
        setCubitQuat(i,index);
956
        }
957
      else if( mCubits[i].getType()==TwistyObjectCubit.TYPE_FOLLOWER )
958
        {
959
        mCubits[i].computeRotationRow();
960
        setCubitQuat(i,mCubits[i].mQuatIndex);
961
        }
962
      }
963
    }
964

    
965
///////////////////////////////////////////////////////////////////////////////////////////////////
966

    
967
  private synchronized void setupPosition(int[][] moves)
968
    {
969
    if( moves!=null )
970
      {
971
      int axisIndex, row, rowBitmap, basic, angle;
972

    
973
      for(int[] move: moves)
974
        {
975
        axisIndex= move[0];
976
        rowBitmap= computeBandagedBitmap( move[1],axisIndex);
977
        row      = computeRowFromBitmap( move[1] );
978
        basic    = mBasicAngles[axisIndex][row];
979
        angle    = move[2]*(360/basic);   // this assumes that all layers from
980
                                          // the bitmap have the same BasicAngle.
981
                                          // at the moment this is always true as
982
                                          // there are no bandaged objects with
983
                                          // different per-layer BasicAngles.
984

    
985
        rotateAllCubits(axisIndex,rowBitmap,angle);
986
        }
987

    
988
      for(int i=0; i<mNumCubits; i++)
989
        {
990
        float[] pos = mCubits[i].getCurrentPos();
991
        int len = pos.length/3;
992
        for(int j=0; j<len; j++) clampPos(pos,3*j);
993
        }
994
      }
995
    }
996

    
997
///////////////////////////////////////////////////////////////////////////////////////////////////
998

    
999
  public int getScrambleType()
1000
    {
1001
    return ObjectScrambler.SCRAMBLING_ALGORITHMS;
1002
    }
1003

    
1004
///////////////////////////////////////////////////////////////////////////////////////////////////
1005

    
1006
  public int[][] getMinimalCubiesInRow()
1007
    {
1008
    return null;
1009
    }
1010

    
1011
///////////////////////////////////////////////////////////////////////////////////////////////////
1012

    
1013
  int computeBandagedBitmap(int rowBitmap, int axisIndex)
1014
    {
1015
    if( mIsBandaged )
1016
      {
1017
      int bitmap, initBitmap=0;
1018

    
1019
      while( initBitmap!=rowBitmap )
1020
        {
1021
        initBitmap = rowBitmap;
1022

    
1023
        for(int cubit=0; cubit<mNumCubits; cubit++)
1024
          {
1025
          bitmap = mCubits[cubit].getRotRow(axisIndex);
1026
          if( (rowBitmap & bitmap) != 0 ) rowBitmap |= bitmap;
1027
          }
1028
        }
1029
      }
1030

    
1031
    if( mMinimalCubiesInRow!=null )
1032
      {
1033
      int[] minC = mMinimalCubiesInRow[axisIndex];
1034
      int numL = minC.length;
1035
      int[] numC = new int[numL];
1036

    
1037
      for(int cubit=0; cubit<mNumCubits; cubit++)
1038
        {
1039
        int bitmap = mCubits[cubit].getRotRow(axisIndex);
1040

    
1041
        for(int i=0; i<numL; i++)
1042
          {
1043
          if( (bitmap&0x1)!=0 ) numC[i]++;
1044
          bitmap>>=1;
1045
          }
1046
        }
1047

    
1048
      int bitmap,initBitmap = 0;
1049

    
1050
      while( initBitmap!=rowBitmap )
1051
        {
1052
        initBitmap = rowBitmap;
1053
        bitmap = rowBitmap;
1054
        int last = 0;
1055

    
1056
        for(int i=0; i<numL; i++)
1057
          {
1058
          if( numC[i]<minC[i] && numC[i]>0 )
1059
            {
1060
            if( (bitmap&0x1)!=0 )
1061
              {
1062
              if( i>0     ) rowBitmap |= (1<<(i-1));
1063
              if( i<numL-1) rowBitmap |= (1<<(i+1));
1064
              }
1065
            else if( (bitmap&0x2)!=0 || last>0 ) // we are not rotating this row, but
1066
                                                 // we are rotating the next or the prev
1067
              {
1068
              rowBitmap |= (1<<i);
1069
              }
1070
            }
1071

    
1072
          last = (bitmap&0x1);
1073
          bitmap>>=1;
1074
          }
1075
        }
1076
      }
1077

    
1078
    return rowBitmap;
1079
    }
1080

    
1081
///////////////////////////////////////////////////////////////////////////////////////////////////
1082

    
1083
  private int computeRowFromBitmap(int rowBitmap)
1084
    {
1085
    int index = 0;
1086

    
1087
    while(index<32)
1088
      {
1089
      if( (rowBitmap&0x1) != 0 ) return index;
1090
      rowBitmap>>=1;
1091
      index++;
1092
      }
1093
    return 0;
1094
    }
1095

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

    
1100
  void clampPos(float[] pos, int offset)
1101
    {
1102
    float currError, minError = Float.MAX_VALUE;
1103
    int minErrorIndex1 = -1;
1104
    int minErrorIndex2 = -1;
1105

    
1106
    float x = pos[offset  ];
1107
    float y = pos[offset+1];
1108
    float z = pos[offset+2];
1109

    
1110
    float xo,yo,zo;
1111

    
1112
    for(int i=0; i<mNumCubits; i++)
1113
      {
1114
      int len = mOrigPos[i].length / 3;
1115

    
1116
      for(int j=0; j<len; j++)
1117
        {
1118
        xo = mOrigPos[i][3*j  ];
1119
        yo = mOrigPos[i][3*j+1];
1120
        zo = mOrigPos[i][3*j+2];
1121

    
1122
        currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
1123

    
1124
        if( currError<minError )
1125
          {
1126
          minError = currError;
1127
          minErrorIndex1 = i;
1128
          minErrorIndex2 = j;
1129
          }
1130
        }
1131
      }
1132

    
1133
    if( minError< 0.05f ) // TODO: 0.05 ?
1134
      {
1135
      pos[offset  ] = mOrigPos[minErrorIndex1][3*minErrorIndex2  ];
1136
      pos[offset+1] = mOrigPos[minErrorIndex1][3*minErrorIndex2+1];
1137
      pos[offset+2] = mOrigPos[minErrorIndex1][3*minErrorIndex2+2];
1138
      }
1139
    }
1140

    
1141
///////////////////////////////////////////////////////////////////////////////////////////////////
1142

    
1143
  void setLibInterface(ObjectLibInterface inter)
1144
    {
1145
    mInterface = inter;
1146
    }
1147

    
1148
///////////////////////////////////////////////////////////////////////////////////////////////////
1149

    
1150
  void applyScrambles(int[][] moves)
1151
    {
1152
    setupPosition(moves);
1153
    }
1154

    
1155
///////////////////////////////////////////////////////////////////////////////////////////////////
1156

    
1157
  void initializeObject(int[][] moves)
1158
    {
1159
    solve();
1160
    setupPosition(moves);
1161
    }
1162

    
1163
///////////////////////////////////////////////////////////////////////////////////////////////////
1164

    
1165
  private void recomputeFaceOffsets()
1166
    {
1167
    if( mThereAreDeciders )
1168
      {
1169
      for(int i=0; i<mNumPuzzleFaces; i++)
1170
        {
1171
        mRowOffsets[i][0] =0;
1172
        mRowOffsets[i][1] =0;
1173
        mRowOffsets[i][2] =0;
1174
        }
1175

    
1176
      for(int i=0; i<mNumCubits; i++)
1177
        if( mCubits[i].getType()==TwistyObjectCubit.TYPE_DECIDER )
1178
          {
1179
          float[] offset = mCubits[i].getOffset();
1180
          int face = mCubits[i].getPuzzleFace();
1181
          mRowOffsets[face][0] = offset[0];
1182
          mRowOffsets[face][1] = offset[1];
1183
          mRowOffsets[face][2] = offset[2];
1184
          }
1185
      }
1186
    }
1187

    
1188
///////////////////////////////////////////////////////////////////////////////////////////////////
1189

    
1190
  synchronized float removeRotation()
1191
    {
1192
    return mRotation.removeRotation();
1193
    }
1194

    
1195
///////////////////////////////////////////////////////////////////////////////////////////////////
1196

    
1197
  long finishRotation(ObjectPreRender pre, int finishAx, int finishRow, float finishAngle, float avgSpeed)
1198
    {
1199
    int basicAngle = mBasicAngles[finishAx][finishRow];
1200
    return mRotation.finishRotation(pre,finishAx,finishRow,basicAngle,finishAngle,avgSpeed);
1201
    }
1202

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

    
1205
  void continueRotation(float angleInDegrees)
1206
    {
1207
    mRotation.continueRotation(angleInDegrees);
1208
    }
1209

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

    
1212
  synchronized long addRotation(EffectListener listener, int axis, int rowBitmap, int angle, long durationMillis )
1213
    {
1214
    return mRotation.addRotation(listener,axis,rowBitmap,angle,durationMillis);
1215
    }
1216

    
1217
///////////////////////////////////////////////////////////////////////////////////////////////////
1218

    
1219
  void enableGhostAxis(int axNum, boolean enable)
1220
    {
1221
    mTouchControl.enableGhostAxis(axNum,enable);
1222
    }
1223

    
1224
///////////////////////////////////////////////////////////////////////////////////////////////////
1225

    
1226
  synchronized boolean beginRotation(int axis, int row )
1227
    {
1228
    if( axis<0 || axis>=mNumAxis )
1229
      {
1230
      android.util.Log.e("object", "invalid rotation axis: "+axis);
1231
      return false;
1232
      }
1233

    
1234
    return mRotation.beginRotation(axis,row);
1235
    }
1236

    
1237
///////////////////////////////////////////////////////////////////////////////////////////////////
1238

    
1239
  void setTextureMap(int cubit, int face, int color)
1240
    {
1241
    int variant  = getCubitVariant(cubit,mNumLayers);
1242
    int shape    = getVariantStickerShape(variant,face);
1243
    int texIndex = color<0 || shape<0 ? mNumTextures-mNumOverrides : shape*mNumFaceColors + color;
1244
    int row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1245
    int col      = texIndex%mNumTexCols;
1246

    
1247
    final float ratioW = 1.0f/mNumTexCols;
1248
    final float ratioH = 1.0f/mNumTexRows;
1249
    final Static4D[] maps = new Static4D[1];
1250
    maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1251
    mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1252
    }
1253

    
1254
///////////////////////////////////////////////////////////////////////////////////////////////////
1255
// figure out the whole face to which (cubit,face) belongs, repaint all (cubit,face) pairs to
1256
// the new color.
1257

    
1258
  void repaintPuzzleFace(int cubit, int face, int newColor)
1259
    {
1260
    int oldColorIndex = getCubitFaceColor(cubit,face);
1261
    int oldColor = mColorTable[oldColorIndex];
1262

    
1263
    if( oldColor!=newColor )
1264
      {
1265
      changeColorInTexture(oldColor,newColor);
1266
      mColorTable[oldColorIndex] = newColor;
1267
      setTexture();
1268
      }
1269
    }
1270

    
1271
///////////////////////////////////////////////////////////////////////////////////////////////////
1272

    
1273
  int getCubitFaceColor(int cubit, int face)
1274
    {
1275
    int puzzleFace = getCubitFaceMap(cubit,face);
1276
    if( puzzleFace>=0 ) puzzleFace %= mNumFaceColors;
1277
    return puzzleFace;
1278
    }
1279

    
1280
///////////////////////////////////////////////////////////////////////////////////////////////////
1281

    
1282
  public int getCubitFaceMap(int cubit, int face)
1283
    {
1284
    int numFaces = mCubitFaceColors[cubit].length;
1285
    int puzzleFace = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
1286
    return puzzleFace<0 ? -1 : puzzleFace;
1287
    }
1288

    
1289
///////////////////////////////////////////////////////////////////////////////////////////////////
1290
// this would return the REAL value of outer; the 'getCubitFaceColor()>=0' returns if the face is
1291
// colored, which for example in case of Container (which has colored internal faces) is not the
1292
// same thing
1293

    
1294
  boolean faceIsOuter(int cubit, int face)
1295
    {
1296
    if( mCubitFaceColors==null )
1297
      {
1298
      if( mShapes==null )
1299
        {
1300
        mShapes = new ObjectShape[mNumCubitVariants];
1301
        }
1302

    
1303
      if( mShapes[0]==null )
1304
        {
1305
        for(int v=0; v<mNumCubitVariants; v++) mShapes[v] = getObjectShape(v);
1306
        }
1307

    
1308
      if( mOrigQuat==null )
1309
        {
1310
        mOrigQuat = new Static4D[mNumCubits];
1311
        }
1312

    
1313
      if( mOrigQuat[0]==null )
1314
        {
1315
        for(int c=0; c<mNumCubits; c++) mOrigQuat[c] = getCubitQuats(c,mNumLayers);
1316
        }
1317

    
1318
      mCubitFaceColors = ObjectShape.computeColors(mShapes,mOrigPos,mOrigQuat,this);
1319
      }
1320

    
1321
    return mCubitFaceColors[cubit][face]>=0;
1322
    }
1323

    
1324
///////////////////////////////////////////////////////////////////////////////////////////////////
1325

    
1326
  void resetAllTextureMaps()
1327
    {
1328
    final float ratioW = 1.0f/mNumTexCols;
1329
    final float ratioH = 1.0f/mNumTexRows;
1330
    int cubColor, stiShape, texIndex, variant, row, col;
1331

    
1332
    for(int cubit=0; cubit<mNumCubits; cubit++)
1333
      {
1334
      final Static4D[] maps = new Static4D[mNumCubitFaces];
1335
      variant = getCubitVariant(cubit,mNumLayers);
1336

    
1337
      for(int face=0; face<mNumCubitFaces; face++)
1338
        {
1339
        cubColor = getCubitFaceColor(cubit,face);
1340
        stiShape = getVariantStickerShape(variant,face);
1341
        texIndex = cubColor<0 || stiShape<0 ? mNumTextures-mNumOverrides : stiShape*mNumFaceColors + cubColor;
1342
        row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1343
        col      = texIndex%mNumTexCols;
1344

    
1345
        maps[face] = new Static4D( col*ratioW, row*ratioH, ratioW, ratioH);
1346
        }
1347

    
1348
      mMesh.setTextureMap(maps,mNumCubitFaces*cubit);
1349
      }
1350

    
1351
    overrideCubitFaceColor();
1352
    }
1353

    
1354
///////////////////////////////////////////////////////////////////////////////////////////////////
1355

    
1356
  private void overrideCubitFaceColor()
1357
    {
1358
    final float ratioW = 1.0f/mNumTexCols;
1359
    final float ratioH = 1.0f/mNumTexRows;
1360

    
1361
    for(int i=0; i<mNumOverrides; i++)
1362
      {
1363
      int[] cubitFaces = mStickerOverrides[i].getCubitFaces();
1364
      int length = cubitFaces.length/2;
1365

    
1366
      for(int j=0; j<length; j++)
1367
        {
1368
        final Static4D[] maps = new Static4D[1];
1369
        int color = mNumTextures-mNumOverrides+1+i;
1370
        int row   = (mNumTexRows-1) - color/mNumTexCols;
1371
        int col   = color%mNumTexCols;
1372
        int cubit = cubitFaces[2*j];
1373
        int face  = cubitFaces[2*j+1];
1374
        maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1375
        mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1376
        }
1377
      }
1378
    }
1379

    
1380
///////////////////////////////////////////////////////////////////////////////////////////////////
1381

    
1382
  void releaseResources()
1383
    {
1384
    mTexture.markForDeletion();
1385
    mMesh.markForDeletion();
1386
    mEffects.markForDeletion();
1387

    
1388
    if( mBitmap!=null )
1389
      {
1390
      mBitmap.recycle();
1391
      mBitmap = null;
1392
      }
1393

    
1394
    for(int j=0; j<mNumCubits; j++)
1395
      {
1396
      mCubits[j].releaseResources();
1397
      }
1398
    }
1399

    
1400
///////////////////////////////////////////////////////////////////////////////////////////////////
1401

    
1402
  private void setCubitQuat(int cubit, int equAssociation)
1403
    {
1404
    int andAssociation = mCubits[cubit].computeAssociation();
1405

    
1406
    if( !mIsInMixupMode )
1407
      {
1408
      mMesh.setEffectAssociation(cubit,andAssociation,equAssociation);
1409
      }
1410
    else
1411
      {
1412
      mMesh.setEffectAssociation(cubit,andAssociation,cubit);
1413
      Static4D tmp = mObjectQuats[equAssociation];
1414
      mMixupModeQuats[cubit].set(tmp);
1415
      }
1416
    }
1417

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

    
1420
  synchronized void restorePreferences(OperatingSystemInterface os)
1421
    {
1422
    boolean error = false;
1423
    String key = getShortName();
1424

    
1425
    for(int i=0; i<mNumCubits; i++)
1426
      {
1427
      mQuatDebug[i] = mCubits[i].restorePreferences(key,os);
1428

    
1429
      if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1430
        {
1431
        boolean result = mCubits[i].rotateCubit(mObjectQuats[mQuatDebug[i]],true);
1432
        if( !result ) debugQuat(mObjectQuats[mQuatDebug[i]],i,0,0,0,mQuatDebug[i],3);
1433
        }
1434
      else { error = true; break; }
1435
      }
1436

    
1437
    if( !error )
1438
      {
1439
      recomputeFaceOffsets();
1440

    
1441
      for(int i=0; i<mNumCubits; i++)
1442
        {
1443
        if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1444
          {
1445
          mCubits[i].computeRotationRow();
1446
          setCubitQuat(i,mQuatDebug[i]);
1447
          }
1448
        else { error = true; break; }
1449
        }
1450
      }
1451

    
1452
    if( error )
1453
      {
1454
      for(int i=0; i<mNumCubits; i++)
1455
        {
1456
        mCubits[i].solve();
1457
        setCubitQuat(i,0);
1458
        }
1459
      }
1460
    }
1461

    
1462
///////////////////////////////////////////////////////////////////////////////////////////////////
1463

    
1464
  void savePreferences(OperatingSystemInterface os)
1465
    {
1466
    String key = getShortName();
1467
    for(int i=0; i<mNumCubits; i++) mCubits[i].savePreferences(key,os);
1468
    }
1469

    
1470
///////////////////////////////////////////////////////////////////////////////////////////////////
1471

    
1472
  public void removePreferences(OperatingSystemInterface os)
1473
    {
1474
    String key = getShortName();
1475
    for(int i=0; i<mNumCubits; i++) mCubits[i].removePreferences(key,os);
1476
    }
1477

    
1478
///////////////////////////////////////////////////////////////////////////////////////////////////
1479

    
1480
  private float computeRadiusCorrection(float[][] sticker, int curr, int len)
1481
    {
1482
    final float A = 0.8f;  // 0<A<1
1483

    
1484
    int prev = curr>0 ? curr-1 : len-1;
1485
    int next = curr<len-1 ? curr+1 : 0;
1486

    
1487
    float v1x = sticker[prev][0]-sticker[curr][0];
1488
    float v1y = sticker[prev][1]-sticker[curr][1];
1489
    float v2x = sticker[next][0]-sticker[curr][0];
1490
    float v2y = sticker[next][1]-sticker[curr][1];
1491

    
1492
    float len1= v1x*v1x+v1y*v1y;
1493
    float len2= v2x*v2x+v2y*v2y;
1494

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

    
1497
    return 1-A*cos;
1498
    }
1499

    
1500
///////////////////////////////////////////////////////////////////////////////////////////////////
1501
// Radius of the sphere circumscribed on the puzzle. Needed for pillowing.
1502
//
1503
// This won't work correctly for pillowing off-center puzzles (e.g. mirrors) - for those we'd need
1504
// to introduce the concept of a 'sink center' as well.
1505
//
1506
// public because needed in TouchControlShapemod
1507

    
1508
  public float getCircumscribedRadius()
1509
    {
1510
    switch(mNumPuzzleFaces)
1511
      {
1512
      case  4: return (SQ6/4)*mSize;
1513
      case  6: return (SQ3/2)*mSize;
1514
      case  8: return (SQ2/2)*mSize;
1515
      case 12: return (SQ3/2)*((SQ5+1)/2)*mSize;
1516
      case 16: return 0.50f*mSize;
1517
      }
1518

    
1519
    return 0.0f;
1520
    }
1521

    
1522
///////////////////////////////////////////////////////////////////////////////////////////////////
1523
// might be overridden in subclasses which want per-edge radii
1524

    
1525
  protected float[][][] getStickerRadii()
1526
    {
1527
    float radius = getStickerRadius();
1528
    int numStickers = mStickerCoords.length;
1529
    float[][][] ret = new float[numStickers][][];
1530

    
1531
    for(int s=0; s<numStickers; s++)
1532
      {
1533
      int numLoops = mStickerCoords[s].length;
1534
      ret[s] = new float[numLoops][];
1535

    
1536
      for(int l=0; l<numLoops; l++)
1537
        {
1538
        int numVertices = mStickerCoords[s][l].length;
1539
        ret[s][l] = new float[numVertices];
1540
        for(int v=0; v<numVertices; v++) ret[s][l][v] = radius;
1541
        }
1542
      }
1543

    
1544
    return ret;
1545
    }
1546

    
1547
///////////////////////////////////////////////////////////////////////////////////////////////////
1548
// might be overridden in subclasses which want per-edge strokes
1549

    
1550
  protected float[][][] getStickerStrokes()
1551
    {
1552
    float stroke = getStickerStroke();
1553
    int numStickers = mStickerCoords.length;
1554
    float[][][] ret = new float[numStickers][][];
1555

    
1556
    for(int s=0; s<numStickers; s++)
1557
      {
1558
      int numLoops = mStickerCoords[s].length;
1559
      ret[s] = new float[numLoops][];
1560

    
1561
      for(int l=0; l<numLoops; l++)
1562
        {
1563
        int numVertices = mStickerCoords[s][l].length;
1564
        ret[s][l] = new float[numVertices];
1565
        for(int v=0; v<numVertices; v++) ret[s][l][v] = stroke;
1566
        }
1567
      }
1568

    
1569
    return ret;
1570
    }
1571

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

    
1574
  public ObjectSticker retSticker(int sticker)
1575
    {
1576
    if( mStickers==null )
1577
      {
1578
      float[][][] radii   = getStickerRadii();
1579
      float[][][] strokes = getStickerStrokes();
1580
      float[][][] angles  = getStickerAngles();
1581
      int numStickers = mStickerCoords.length;
1582
      mStickers = new ObjectSticker[numStickers];
1583

    
1584
      for(int s=0; s<numStickers; s++)
1585
        {
1586
        float scale = mStickerScales.length>s ? mStickerScales[s] : 1.0f;
1587
        int numLoops = mStickerCoords[s].length;
1588
        float[][] rad = new float[numLoops][];
1589
        float[][] str = new float[numLoops][];
1590

    
1591
        for(int l=0; l<numLoops; l++)
1592
          {
1593
          int numVerts = mStickerCoords[s][l].length;
1594
          rad[l] = new float[numVerts];
1595
          str[l] = new float[numVerts];
1596
          float[] st = strokes[s][l];
1597
          float[] ra = radii[s][l];
1598

    
1599
          for(int v=0; v<numVerts; v++)
1600
            {
1601
            rad[l][v] = ra[v] * computeRadiusCorrection(mStickerCoords[s][l],v,numVerts) / scale;
1602
            str[l][v] = st[v] / scale;
1603
            }
1604
          }
1605

    
1606
        mStickers[s] = new ObjectSticker(mStickerCoords[s], (angles==null ? null : angles[s]) ,rad, str);
1607
        }
1608
      }
1609

    
1610
    return mStickers[sticker];
1611
    }
1612

    
1613
///////////////////////////////////////////////////////////////////////////////////////////////////
1614
// some objects (currently Kilominx,Ivy,Rex) might want to change the stickers.
1615

    
1616
  public void adjustStickerCoords()
1617
    {
1618

    
1619
    }
1620

    
1621
///////////////////////////////////////////////////////////////////////////////////////////////////
1622

    
1623
  public Static4D[] getQuats()
1624
    {
1625
    if( mObjectQuats==null )
1626
      {
1627
      mObjectQuats = QuatGroupGenerator.computeGroup(mAxis,mBasicAngles);
1628
      }
1629

    
1630
    return mObjectQuats;
1631
    }
1632

    
1633
///////////////////////////////////////////////////////////////////////////////////////////////////
1634

    
1635
  public int[][] getVariantFaceIsOuter()
1636
    {
1637
    return mVariantFaceIsOuter;
1638
    }
1639

    
1640
///////////////////////////////////////////////////////////////////////////////////////////////////
1641

    
1642
  public int getInternalColor()
1643
    {
1644
    return COLOR_INTERNAL;
1645
    }
1646

    
1647
///////////////////////////////////////////////////////////////////////////////////////////////////
1648
// the getFaceColors + final INTERNAL_COLOR in a grid (so that we do not exceed the maximum texture size)
1649

    
1650
  void createTexture(float borders, float corners)
1651
    {
1652
    Paint paint = new Paint();
1653
    Canvas canvas = new Canvas(mBitmap);
1654

    
1655
    paint.setAntiAlias(true);
1656
    paint.setTextAlign(Paint.Align.CENTER);
1657
    paint.setStyle(Paint.Style.FILL);
1658
    paint.setColor(getInternalColor());
1659
    canvas.drawRect(0, 0, mNumTexCols*mTexHeight, mNumTexRows*mTexHeight, paint);
1660

    
1661
    mTextureBorderMultiplier = borders;
1662
    mTextureCornerMultiplier = corners;
1663

    
1664
    int texture = 0;
1665
    FactorySticker factory = FactorySticker.getInstance();
1666

    
1667
    for(int row=0; row<mNumTexRows; row++)
1668
      for(int col=0; col<mNumTexCols; col++)
1669
        {
1670
        if( texture<mNumTextures-mNumOverrides )
1671
          {
1672
          ObjectSticker sticker = retSticker(texture/mNumFaceColors);
1673
          int color = mColorTable[texture%mNumFaceColors];
1674
          factory.drawRoundedPolygons( canvas, paint, col*mTexHeight, (mNumTexRows-row)*mTexHeight, color,
1675
                                       mTexHeight, sticker,mTextureBorderMultiplier,mTextureCornerMultiplier);
1676
          }
1677
        else if( texture>mNumTextures-mNumOverrides && texture<=mNumTextures )
1678
          {
1679
          int color = mStickerOverrides[mNumTextures-texture].getColor();
1680
          factory.drawSolidColor(canvas, paint, col*mTexHeight, (mNumTexRows-row)*mTexHeight, color, mTexHeight);
1681
          }
1682

    
1683
        texture++;
1684
        }
1685
    }
1686

    
1687
///////////////////////////////////////////////////////////////////////////////////////////////////
1688

    
1689
  private void changeColorInTexture(int oldColor, int newColor)
1690
    {
1691
    Paint paint = new Paint();
1692
    Canvas canvas = new Canvas(mBitmap);
1693
    paint.setAntiAlias(true);
1694
    int texture = 0;
1695
    FactorySticker factory = FactorySticker.getInstance();
1696

    
1697
    for(int row=0; row<mNumTexRows; row++)
1698
      for(int col=0; col<mNumTexCols; col++)
1699
        {
1700
        if( texture<mNumTextures-mNumOverrides )
1701
          {
1702
          int color = mColorTable[texture%mNumFaceColors];
1703

    
1704
          if( color==oldColor )
1705
            {
1706
            ObjectSticker sticker = retSticker(texture/mNumFaceColors);
1707
            factory.drawRoundedPolygons( canvas, paint, col*mTexHeight, (mNumTexRows-row)*mTexHeight,
1708
                                         newColor, mTexHeight, sticker,mTextureBorderMultiplier,mTextureCornerMultiplier);
1709
            }
1710
          }
1711

    
1712
        texture++;
1713
        }
1714
    }
1715

    
1716
///////////////////////////////////////////////////////////////////////////////////////////////////
1717

    
1718
  void setTexture()
1719
    {
1720
    if( mBitmap==null )
1721
      {
1722
      mBitmap = Bitmap.createBitmap( mNumTexCols*mTexHeight, mNumTexRows*mTexHeight, Bitmap.Config.ARGB_4444);
1723
      createTexture(1.0f,1.0f);
1724
      }
1725

    
1726
    if( !mTexture.setTextureAlreadyInverted(mBitmap) )
1727
      {
1728
      int max = DistortedLibrary.getMaxTextureSize();
1729
      mInterface.reportProblem("failed to set texture of size "+mBitmap.getWidth()+"x"+mBitmap.getHeight()+" max is "+max, true);
1730
      }
1731
    }
1732

    
1733
///////////////////////////////////////////////////////////////////////////////////////////////////
1734

    
1735
  void setObjectRatioNow(float sc, int nodeSize)
1736
    {
1737
    mObjectScreenRatio = sc;
1738
    float scale = mObjectScreenRatio*mInitScreenRatio*nodeSize/mSize;
1739
    mObjectScale.set(scale,scale,scale);
1740

    
1741
    if( mTouchControl ==null ) mTouchControl = getTouchControl();
1742
    mTouchControl.setObjectRatio(mObjectScreenRatio*mInitScreenRatio);
1743
    }
1744

    
1745
///////////////////////////////////////////////////////////////////////////////////////////////////
1746

    
1747
  void setObjectRatio(float sizeChange, int nodeSize)
1748
    {
1749
    mObjectScreenRatio *= (1.0f+sizeChange)/2;
1750

    
1751
    if( mObjectScreenRatio>MAX_SIZE_CHANGE) mObjectScreenRatio = MAX_SIZE_CHANGE;
1752
    if( mObjectScreenRatio<MIN_SIZE_CHANGE) mObjectScreenRatio = MIN_SIZE_CHANGE;
1753

    
1754
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1755
    }
1756

    
1757
///////////////////////////////////////////////////////////////////////////////////////////////////
1758

    
1759
  void setNodeSize(int nodeSize)
1760
    {
1761
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1762
    }
1763

    
1764
///////////////////////////////////////////////////////////////////////////////////////////////////
1765

    
1766
  public float getRatio()
1767
    {
1768
    return mObjectScreenRatio;
1769
    }
1770

    
1771
///////////////////////////////////////////////////////////////////////////////////////////////////
1772

    
1773
  public float getObjectRatio()
1774
    {
1775
    return mObjectScreenRatio*mInitScreenRatio;
1776
    }
1777

    
1778
///////////////////////////////////////////////////////////////////////////////////////////////////
1779

    
1780
  boolean isSolved()
1781
    {
1782
    boolean solved = mSolved.isSolved(mCubits);
1783
    return mRotation.isSolved(solved);
1784
    }
1785

    
1786
///////////////////////////////////////////////////////////////////////////////////////////////////
1787
// INTERNAL API - those are called from 'effects' package
1788
///////////////////////////////////////////////////////////////////////////////////////////////////
1789

    
1790
  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
1791
    {
1792
    mScrambler.randomizeNewScramble(scramble,rnd,curr,total, getSignature() );
1793
    }
1794

    
1795
///////////////////////////////////////////////////////////////////////////////////////////////////
1796

    
1797
  public Static4D getRotationQuat()
1798
    {
1799
    return mQuat;
1800
    }
1801

    
1802
///////////////////////////////////////////////////////////////////////////////////////////////////
1803

    
1804
  public float getSize()
1805
    {
1806
    return mSize;
1807
    }
1808

    
1809
///////////////////////////////////////////////////////////////////////////////////////////////////
1810

    
1811
  public void applyEffect(Effect effect, int position)
1812
    {
1813
    mEffects.apply(effect, position);
1814
    }
1815

    
1816
///////////////////////////////////////////////////////////////////////////////////////////////////
1817

    
1818
  public void removeEffect(long effectID)
1819
    {
1820
    mEffects.abortById(effectID);
1821
    }
1822

    
1823
///////////////////////////////////////////////////////////////////////////////////////////////////
1824

    
1825
  public MeshBase getObjectMesh()
1826
    {
1827
    return mMesh;
1828
    }
1829

    
1830
///////////////////////////////////////////////////////////////////////////////////////////////////
1831

    
1832
  public DistortedEffects getObjectEffects()
1833
    {
1834
    return mEffects;
1835
    }
1836

    
1837
///////////////////////////////////////////////////////////////////////////////////////////////////
1838

    
1839
  public int getCubitType(int cubit)
1840
    {
1841
    return mCubits[cubit].getType();
1842
    }
1843

    
1844
///////////////////////////////////////////////////////////////////////////////////////////////////
1845

    
1846
  public float[] getCubitOffset(int cubit)
1847
    {
1848
    return mCubits[cubit].getOffset();
1849
    }
1850

    
1851
///////////////////////////////////////////////////////////////////////////////////////////////////
1852

    
1853
  public ObjectStickerOverride[] getStickerOverrides()
1854
    {
1855
    return null;
1856
    }
1857

    
1858
///////////////////////////////////////////////////////////////////////////////////////////////////
1859

    
1860
  public boolean getError()
1861
    {
1862
    return mError;
1863
    }
1864

    
1865
///////////////////////////////////////////////////////////////////////////////////////////////////
1866

    
1867
  public String getErrorString()
1868
    {
1869
    return mErrorString;
1870
    }
1871

    
1872
///////////////////////////////////////////////////////////////////////////////////////////////////
1873
// PUBLIC API
1874
///////////////////////////////////////////////////////////////////////////////////////////////////
1875

    
1876
  public int getCubitFaceStickerIndex(int cubit, int face)
1877
    {
1878
    Static4D texMap = mMesh.getTextureMap(mNumCubitFaces*cubit + face);
1879

    
1880
    int x = (int)(texMap.get0()/texMap.get2());
1881
    int y = (int)(texMap.get1()/texMap.get3());
1882

    
1883
    return (mNumTexRows-1-y)*mNumTexCols + x;
1884
    }
1885

    
1886
///////////////////////////////////////////////////////////////////////////////////////////////////
1887

    
1888
  public int[] getNumLayers()
1889
    {
1890
    return mNumLayers;
1891
    }
1892

    
1893
///////////////////////////////////////////////////////////////////////////////////////////////////
1894

    
1895
  public synchronized void solve()
1896
    {
1897
    for(int i=0; i<mNumCubits; i++) mCubits[i].solve();
1898

    
1899
    recomputeFaceOffsets();
1900

    
1901
    for(int i=0; i<mNumCubits; i++)
1902
      {
1903
      mCubits[i].computeRotationRow();
1904
      setCubitQuat(i,0);
1905
      }
1906
    }
1907

    
1908
///////////////////////////////////////////////////////////////////////////////////////////////////
1909

    
1910
  public int getCubitQuatIndex(int cubit)
1911
    {
1912
    return (cubit>=0 && cubit<mNumCubits) ? mCubits[cubit].mQuatIndex : 0;
1913
    }
1914

    
1915
///////////////////////////////////////////////////////////////////////////////////////////////////
1916

    
1917
  public int getCubitRotRow(int cubit, int axis)
1918
    {
1919
    return mCubits[cubit].getRotRow(axis);
1920
    }
1921

    
1922
///////////////////////////////////////////////////////////////////////////////////////////////////
1923

    
1924
  public Bitmap getStickerBitmap()
1925
    {
1926
    return mBitmap;
1927
    }
1928

    
1929
///////////////////////////////////////////////////////////////////////////////////////////////////
1930

    
1931
  public DistortedNode getNode()
1932
    {
1933
    return mNode;
1934
    }
1935

    
1936
///////////////////////////////////////////////////////////////////////////////////////////////////
1937

    
1938
  public int getNumStickerTypes()
1939
    {
1940
    return mNumStickerTypes;
1941
    }
1942

    
1943
///////////////////////////////////////////////////////////////////////////////////////////////////
1944

    
1945
  public String reportState()
1946
    {
1947
    StringBuilder builder = new StringBuilder();
1948

    
1949
    for(int i=0; i<mNumCubits; i++ )
1950
      {
1951
      if( i>0 ) builder.append('.');
1952
      builder.append(mCubits[i].mQuatIndex);
1953
      }
1954

    
1955
    return builder.toString();
1956
    }
1957

    
1958
///////////////////////////////////////////////////////////////////////////////////////////////////
1959
// this is here only so it can be overridden in TwistyJSON so that we can get this from JSON.
1960

    
1961
  public int getNumCubitFaces()
1962
    {
1963
    return 0;
1964
    }
1965

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

    
1971
  public float getPillowCoeff()
1972
    {
1973
    return 1.0f;
1974
    }
1975

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

    
1978
  public TouchControl getTouchControl()
1979
    {
1980
    if( mTouchControl==null )
1981
      {
1982
      switch(getTouchControlType())
1983
        {
1984
        case TC_TETRAHEDRON      : mTouchControl = new TouchControlTetrahedron(this);
1985
                                   break;
1986
        case TC_HEXAHEDRON       : mTouchControl = new TouchControlHexahedron(this);
1987
                                   break;
1988
        case TC_OCTAHEDRON       : mTouchControl = new TouchControlOctahedron(this);
1989
                                   break;
1990
        case TC_DODECAHEDRON     : mTouchControl = new TouchControlDodecahedron(this);
1991
                                   break;
1992
        case TC_ICOSAHEDRON      : mTouchControl = new TouchControlIcosahedron(this);
1993
                                   break;
1994
        case TC_CUBOID           : int[] numLayers = getNumLayers();
1995
                                   mTouchControl = new TouchControlCuboids(this,getDist3D(numLayers));
1996
                                   break;
1997
        case TC_BALL             : mTouchControl = new TouchControlBall(this);
1998
                                   break;
1999
        case TC_CHANGING_MIRROR  : mTouchControl = new TouchControlMirror(this);
2000
                                   break;
2001
        case TC_CHANGING_SQUARE  : mTouchControl = new TouchControlSquare(this);
2002
                                   break;
2003
        case TC_CHANGING_SHAPEMOD: mTouchControl = new TouchControlShapemod(this);
2004
                                   break;
2005
        }
2006
      }
2007
    return mTouchControl;
2008
    }
2009

    
2010
///////////////////////////////////////////////////////////////////////////////////////////////////
2011

    
2012
  public float[][] returnRotationFactor()
2013
    {
2014
    float[][] factor = new float[mNumAxis][];
2015

    
2016
    for(int ax=0; ax<mNumAxis; ax++)
2017
      {
2018
      int numL = mNumLayers[ax];
2019
      factor[ax] = new float[numL];
2020
      for(int la=0; la<numL; la++) factor[ax][la] = 1.0f;
2021
      }
2022

    
2023
    return factor;
2024
    }
2025

    
2026
///////////////////////////////////////////////////////////////////////////////////////////////////
2027

    
2028
  protected void setReader(JsonReader reader)
2029
    {
2030
    // empty
2031
    }
2032

    
2033
///////////////////////////////////////////////////////////////////////////////////////////////////
2034

    
2035
  public String getInventor()
2036
    {
2037
    return mMetadata.getAuthor();
2038
    }
2039

    
2040
///////////////////////////////////////////////////////////////////////////////////////////////////
2041

    
2042
  public int getYearOfInvention()
2043
    {
2044
    return mMetadata.getYearOfInvention();
2045
    }
2046

    
2047
///////////////////////////////////////////////////////////////////////////////////////////////////
2048

    
2049
  public float getComplexity()
2050
    {
2051
    return mMetadata.getDifficulty();
2052
    }
2053

    
2054
///////////////////////////////////////////////////////////////////////////////////////////////////
2055

    
2056
  public String getObjectName()
2057
    {
2058
    return mMetadata.getObjectName();
2059
    }
2060

    
2061
///////////////////////////////////////////////////////////////////////////////////////////////////
2062

    
2063
  public ObjectSignature getSignature()
2064
    {
2065
    return mMetadata.getSignature();
2066
    }
2067

    
2068
///////////////////////////////////////////////////////////////////////////////////////////////////
2069
  // for JSON only
2070
  public abstract int getTouchControlType();
2071
  public abstract int getTouchControlSplit();
2072
  public abstract boolean[][] getLayerRotatable(int[] numLayers);
2073
  public abstract int[][][] getEnabled();
2074
  public abstract float[] getDist3D(int[] numLayers);
2075
  public abstract Static3D[] getFaceAxis();
2076
  public abstract int[][] getScrambleEdges();
2077
  public abstract float[][] getCuts(int[] numLayers);
2078
  public abstract float getStickerRadius();
2079
  public abstract float getStickerStroke();
2080
  public abstract float[][][] getStickerAngles();
2081
  public abstract int getCubitVariant(int cubit, int[] numLayers);
2082
  public abstract ObjectShape getObjectShape(int variant);
2083
  public abstract ObjectFaceShape getObjectFaceShape(int variant);
2084
  public abstract ObjectVertexEffects getVertexEffects(int variant);
2085
  public abstract int getNumCubitVariants(int[] numLayers);
2086
  public abstract float[][] getCubitPositions(int[] numLayers);
2087
  public abstract Static4D getCubitQuats(int cubit, int[] numLayers);
2088
  public abstract float getScreenRatio();
2089
  public abstract int[] getColorTable();
2090
  public abstract String getShortName();
2091

    
2092
  // not only for JSON
2093
  public abstract Static3D[] getRotationAxis();
2094
  public abstract int[][] getBasicAngles();
2095
  public abstract int getNumFaces();
2096
  public abstract int getFOV();
2097
  public abstract String[][] getTutorials();
2098
  }
(6-6/9)