Project

General

Profile

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

distorted-objectlib / src / main / java / org / distorted / objectlib / main / TwistyObject.java @ 51262d81

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
    ScrambleState[] states = getScrambleStates();
243
    mScrambler = new ObjectScrambler(scramblingType, mNumAxis,mNumLayers,states);
244

    
245
    boolean bandaged=false;
246

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

    
258
    mRotationAngle= new Dynamic1D();
259
    mRotationAxis = new Static3D(1,0,0);
260
    mRotateEffect = new VertexEffectRotate(mRotationAngle, mRotationAxis, CENTER);
261

    
262
    mRotationAngleStatic = new Static1D(0);
263
    mRotationAngleMiddle = new Static1D(0);
264
    mRotationAngleFinal  = new Static1D(0);
265

    
266
    mObjectScale = new Static3D(scale,scale,scale);
267
    setObjectRatioNow(scale,720);
268

    
269
    mEffects = new DistortedEffects();
270
    createQuaternionEffects();
271

    
272
    MatrixEffectScale scaleEffect = new MatrixEffectScale(mObjectScale);
273
    MatrixEffectQuaternion quatEffect = new MatrixEffectQuaternion(mQuat, CENTER);
274
    MatrixEffectMove moveEffect = new MatrixEffectMove(move);
275

    
276
    boolean fromDMESH = (meshStream!=null && meshState==MESH_NICE);
277
    getQuatsAndShapes(fromDMESH,fromJSON);
278
    createMeshAndCubits(meshStream,meshState,fromDMESH);
279
    setUpTextures(fromDMESH,fromJSON);
280
    createDataStructuresForSolved();
281

    
282
    mEffects.apply(mRotateEffect);
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
  private void createQuaternionEffects()
293
    {
294
    if( mNumQuats<=ObjectControl.MAX_QUATS )
295
      {
296
      mIsInMixupMode = false;
297

    
298
      for( int q=0; q<mNumQuats; q++)
299
        {
300
        VertexEffectQuaternion vq = new VertexEffectQuaternion(mObjectQuats[q],CENTER);
301
        vq.setMeshAssociation(0,q);
302
        mEffects.apply(vq);
303
        }
304
      }
305
    else if( mNumCubits<=ObjectControl.MAX_QUATS )
306
      {
307
      mIsInMixupMode = true;
308
      mMixupModeQuats = new Static4D[mNumCubits];
309

    
310
      for( int q=0; q<mNumCubits; q++)
311
        {
312
        mMixupModeQuats[q] = new Static4D(mObjectQuats[0]);
313
        VertexEffectQuaternion vq = new VertexEffectQuaternion(mMixupModeQuats[q],CENTER);
314
        vq.setMeshAssociation(0,q);
315
        mEffects.apply(vq);
316
        }
317
      }
318
    else
319
      {
320
      android.util.Log.e("D", "object has too many quaternions ("+mNumQuats+") or too many cubits ("+mNumCubits+")");
321
      }
322
    }
323

    
324
///////////////////////////////////////////////////////////////////////////////////////////////////
325

    
326
  private Static3D getPos(float[] origPos)
327
    {
328
    int len = origPos.length/3;
329
    float sumX = 0.0f;
330
    float sumY = 0.0f;
331
    float sumZ = 0.0f;
332

    
333
    for(int i=0; i<len; i++)
334
      {
335
      sumX += origPos[3*i  ];
336
      sumY += origPos[3*i+1];
337
      sumZ += origPos[3*i+2];
338
      }
339

    
340
    sumX /= len;
341
    sumY /= len;
342
    sumZ /= len;
343

    
344
    return new Static3D(sumX,sumY,sumZ);
345
    }
346

    
347
///////////////////////////////////////////////////////////////////////////////////////////////////
348

    
349
  private void createOuterFaces()
350
    {
351
    for(int v=0; v<mNumCubitVariants; v++)
352
      {
353
      int[][] indices = mShapes[v].getVertIndices();
354
      int faces = indices.length;
355
      mVariantFaceIsOuter[v] = new int[faces];
356
      }
357

    
358
    for( int cubit=0; cubit<mNumCubits; cubit++)
359
      {
360
      int variant = getCubitVariant(cubit,mNumLayers);
361
      int[][] indices = mShapes[variant].getVertIndices();
362
      int numFaces = indices.length;
363

    
364
      for(int face=0; face<numFaces; face++)
365
        if( getCubitFaceColor(cubit,face)>=0 )
366
          {
367
          mVariantFaceIsOuter[variant][face] = 1;
368
          }
369
      }
370
    }
371

    
372
///////////////////////////////////////////////////////////////////////////////////////////////////
373

    
374
  private void getQuatsAndShapes(boolean fromDMESH, boolean fromJSON)
375
    {
376
    mNumCubitVariants = getNumCubitVariants(mNumLayers);
377

    
378
    if( !fromDMESH || !fromJSON )
379
      {
380
      FactoryCubit factory = FactoryCubit.getInstance();
381
      factory.clear();
382

    
383
      mOrigQuat = new Static4D[mNumCubits];
384
      for(int i=0; i<mNumCubits; i++) mOrigQuat[i] = getCubitQuats(i,mNumLayers);
385

    
386
      mShapes = new ObjectShape[mNumCubitVariants];
387
      for(int i=0; i<mNumCubitVariants; i++) mShapes[i] = getObjectShape(i);
388
      mNumCubitFaces = ObjectShape.computeNumComponents(mShapes);
389
      mVariantFaceIsOuter = new int[mNumCubitVariants][];
390

    
391
      if( !fromJSON )
392
        {
393
        mCubitFaceColors = ObjectShape.computeColors(mShapes,mOrigPos,mOrigQuat,this);
394
        createOuterFaces();
395
        }
396

    
397
      if( fromDMESH )
398
        {
399
        for(int i=0; i<mNumCubitVariants; i++) factory.createNewFaceTransform(mShapes[i], mVariantFaceIsOuter[i]);
400
        }
401
      }
402
    }
403

    
404
///////////////////////////////////////////////////////////////////////////////////////////////////
405

    
406
  private void createMeshAndCubits(InputStream stream, int meshState, boolean fromDMESH)
407
    {
408
    mCubits = new Cubit[mNumCubits];
409

    
410
    if( fromDMESH )
411
      {
412
      DataInputStream dos = new DataInputStream(stream);
413
      mMesh = new MeshFile(dos);
414

    
415
      try
416
        {
417
        stream.close();
418
        }
419
      catch(IOException e)
420
        {
421
        android.util.Log.e("meshFile", "Error closing InputStream: "+e.toString());
422
        }
423
      }
424
    else
425
      {
426
      MeshBase[] cubitMesh = new MeshBase[mNumCubits];
427

    
428
      for(int i=0; i<mNumCubits; i++)
429
        {
430
        cubitMesh[i] = createCubitMesh(i,mNumLayers,meshState,mNumCubitFaces);
431
        Static3D pos = getPos(mOrigPos[i]);
432
        cubitMesh[i].apply(new MatrixEffectMove(pos),1,0);
433
        }
434

    
435
      mMesh = new MeshJoined(cubitMesh);
436

    
437
      float pillowCoeff = getPillowCoeff();
438

    
439
      if( pillowCoeff!=1.0f )
440
        {
441
        float radius = getCircumscribedRadius();
442
        Static1D coeff = new Static1D(pillowCoeff);
443
        Static4D region= new Static4D(0,0,0,radius);
444
        VertexEffectSink sink = new VertexEffectSink(coeff,CENTER,region);
445
        mMesh.apply(sink);
446
        }
447
      }
448

    
449
    for(int i=0; i<mNumCubits; i++)
450
      {
451
      mCubits[i] = new Cubit(this,mOrigPos[i],mNumAxis,mMaxNumLayers,i);
452
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
453
      }
454
    }
455

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

    
458
  private MeshBase createCubitMesh(int cubit, int[] numLayers, int meshState, int numComponents)
459
    {
460
    int variant = getCubitVariant(cubit,numLayers);
461

    
462
    if( mMeshes==null ) mMeshes = new MeshBase[mNumCubitVariants];
463

    
464
    if( mMeshes[variant]==null )
465
      {
466
      ObjectFaceShape faceShape = getObjectFaceShape(variant);
467
      ObjectVertexEffects effects = getVertexEffects(variant);
468
      FactoryCubit factory = FactoryCubit.getInstance();
469
      factory.createNewFaceTransform(mShapes[variant],mVariantFaceIsOuter[variant]);
470
      mMeshes[variant] = factory.createRoundedSolid(mShapes[variant],faceShape,effects, meshState, numComponents);
471
      }
472

    
473
    MeshBase mesh = mMeshes[variant].copy(true);
474
    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( mOrigQuat[cubit], CENTER );
475
    mesh.apply(quat,0xffffffff,0);
476

    
477
    return mesh;
478
    }
479

    
480
///////////////////////////////////////////////////////////////////////////////////////////////////
481

    
482
  private void setUpTextures(boolean fromDMESH, boolean fromJSON)
483
    {
484
    mTexture = new DistortedTexture();
485

    
486
    if( fromJSON )
487
      {
488
      mNumStickerTypes = getNumStickerTypes();
489
      mNumCubitFaces = getNumCubitFaces();
490
      }
491
    else
492
      {
493
      FactoryCubit factory = FactoryCubit.getInstance();
494
      mStickerCoords   = factory.getStickerCoords();
495
      mStickerVariants = factory.getStickerVariants();
496
      mStickerScales   = factory.getStickerScales();
497
      adjustStickerCoords();
498
      mNumStickerTypes = (mStickerCoords==null ? 0 : mStickerCoords.length);
499
      }
500

    
501
    mStickerOverrides = getStickerOverrides();
502
    mNumOverrides = mStickerOverrides==null ? 0 : mStickerOverrides.length;
503

    
504
    mNumTextures= mNumFaceColors*mNumStickerTypes + mNumOverrides;
505
    mNumTexCols = NUM_STICKERS_IN_ROW;
506
    mNumTexRows = (mNumTextures+1)/NUM_STICKERS_IN_ROW;
507
    if( mNumTexCols*mNumTexRows < mNumTextures+1 ) mNumTexRows++;
508

    
509
    if( !fromDMESH || shouldResetTextureMaps() ) resetAllTextureMaps();
510
    else overrideCubitFaceColor();
511

    
512
    setTexture();
513
    }
514

    
515
///////////////////////////////////////////////////////////////////////////////////////////////////
516

    
517
  private int getMultQuat(int index1, int index2)
518
    {
519
    if( mQuatMult==null )
520
      {
521
      mQuatMult = new int[mNumQuats][mNumQuats];
522

    
523
      for(int i=0; i<mNumQuats; i++)
524
        for(int j=0; j<mNumQuats; j++) mQuatMult[i][j] = -1;
525
      }
526

    
527
    if( index1<mNumQuats && index2<mNumQuats )
528
      {
529
      if( mQuatMult[index1][index2]==-1 ) mQuatMult[index1][index2] = mulQuat(index1,index2);
530
      return mQuatMult[index1][index2];
531
      }
532

    
533
    return -1;
534
    }
535

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

    
538
  public InitData getInitData()
539
    {
540
    return mInitData;
541
    }
542

    
543
///////////////////////////////////////////////////////////////////////////////////////////////////
544

    
545
  public boolean isInIconMode()
546
    {
547
    return mIconMode==MODE_ICON;
548
    }
549

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

    
552
  public int getVariantStickerShape(int variant, int face)
553
    {
554
    return face>=mStickerVariants[variant].length ? -1 : mStickerVariants[variant][face];
555
    }
556

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

    
559
  public boolean shouldResetTextureMaps()
560
    {
561
    return false;
562
    }
563

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

    
566
  private void createDataStructuresForSolved()
567
    {
568
    mTmpQuats = new int[mNumQuats];
569
    mSolvedQuats = getSolvedQuats();
570
    }
571

    
572
///////////////////////////////////////////////////////////////////////////////////////////////////
573
// This is used to build internal data structures for the generic 'isSolved()'
574
//
575
// if this is an internal cubit (all faces black): return -1
576
// if this is a face cubit (one non-black face): return the color index of the only non-black face.
577
// Color index, i.e. the index into the 'FACE_COLORS' table.
578
// else (edge or corner cubit, more than one non-black face): return -2.
579

    
580
  protected int retCubitSolvedStatus(int cubit)
581
    {
582
    int numNonBlack=0, nonBlackIndex=-1, stiShape, cubColor;
583
    int variant = getCubitVariant(cubit,mNumLayers);
584

    
585
    for(int face=0; face<mNumCubitFaces; face++)
586
      {
587
      stiShape = getVariantStickerShape(variant,face);
588
      int numFaces = mCubitFaceColors[cubit].length;
589
      cubColor = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
590

    
591
      if( stiShape>=0 && cubColor>=0 )
592
        {
593
        numNonBlack++;
594
        nonBlackIndex = cubColor;
595
        }
596
      }
597

    
598
    if( numNonBlack==0 ) return -1;
599
    if( numNonBlack>=2 ) return -2;
600

    
601
    return nonBlackIndex;
602
    }
603

    
604
///////////////////////////////////////////////////////////////////////////////////////////////////
605

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

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

    
618
    return false;
619
    }
620

    
621
///////////////////////////////////////////////////////////////////////////////////////////////////
622

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

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

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

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

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

    
644
    return true;
645
    }
646

    
647
///////////////////////////////////////////////////////////////////////////////////////////////////
648

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

    
656
    return ret;
657
    }
658

    
659
///////////////////////////////////////////////////////////////////////////////////////////////////
660

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

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

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

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

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

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

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

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

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

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

    
716
///////////////////////////////////////////////////////////////////////////////////////////////////
717

    
718
  protected int[] buildSolvedQuats(Static3D faceAx)
719
    {
720
    final float MAXD = 0.0001f;
721
    float x = faceAx.get0();
722
    float y = faceAx.get1();
723
    float z = faceAx.get2();
724
    float a,dx,dy,dz,qx,qy,qz;
725
    Static4D quat;
726
    int place = 0;
727

    
728
    for(int q=1; q<mNumQuats; q++)
729
      {
730
      quat = mObjectQuats[q];
731
      qx = quat.get0();
732
      qy = quat.get1();
733
      qz = quat.get2();
734

    
735
           if( x!=0.0f ) { a = qx/x; }
736
      else if( y!=0.0f ) { a = qy/y; }
737
      else               { a = qz/z; }
738

    
739
      dx = a*x-qx;
740
      dy = a*y-qy;
741
      dz = a*z-qz;
742

    
743
      if( dx>-MAXD && dx<MAXD && dy>-MAXD && dy<MAXD && dz>-MAXD && dz<MAXD )
744
        {
745
        mTmpQuats[place++] = q;
746
        }
747
      }
748

    
749
    if( place!=0 )
750
      {
751
      int[] ret = new int[place];
752
      System.arraycopy(mTmpQuats,0,ret,0,place);
753
      return ret;
754
      }
755

    
756
    return null;
757
    }
758

    
759
///////////////////////////////////////////////////////////////////////////////////////////////////
760

    
761
  public int getCubitRotationType(int cubit)
762
    {
763
    return Cubit.TYPE_NORMAL;
764
    }
765

    
766
///////////////////////////////////////////////////////////////////////////////////////////////////
767

    
768
  float[] getTrackingPoint(int cubitIndex, int cubitType)
769
    {
770
    if( cubitType!=Cubit.TYPE_NORMAL )
771
      {
772
      int variant = getCubitVariant(cubitIndex,mNumLayers);
773

    
774
      // object must have been created from JSON
775
      if( mVariantFaceIsOuter==null || mVariantFaceIsOuter[variant]==null )
776
        {
777
        mVariantFaceIsOuter = getVariantFaceIsOuter();
778
        }
779
      if( mShapes==null )
780
        {
781
        mShapes = new ObjectShape[mNumCubitVariants];
782
        }
783
      if( mShapes[variant]==null )
784
        {
785
        mShapes[variant] = getObjectShape(variant);
786
        }
787
      if( mOrigQuat==null )
788
        {
789
        mOrigQuat = new Static4D[mNumCubits];
790
        }
791
      if( mOrigQuat[cubitIndex]==null )
792
        {
793
        mOrigQuat[cubitIndex] = getCubitQuats(cubitIndex,mNumLayers);
794
        }
795

    
796
      int[][] indices = mShapes[variant].getVertIndices();
797
      int outer=-1, faces = indices.length;
798

    
799
      for(int i=0; i<faces; i++)
800
        {
801
        if( mVariantFaceIsOuter[variant][i]==1 )
802
          {
803
          outer=i;
804
          break;
805
          }
806
        }
807

    
808
      if( outer>=0 )
809
        {
810
        int vertIndex = indices[outer][0];
811
        float[] vertices = mShapes[variant].getVertices()[vertIndex];
812
        float[] ret = new float[3];
813
        float[] curpos = mOrigPos[cubitIndex];
814
        Static4D quat = mOrigQuat[cubitIndex];
815
        QuatHelper.rotateVectorByQuat(mTmp, vertices[0], vertices[1], vertices[2], 1, quat);
816

    
817
        ret[0] = mTmp[0]+computeAvg(curpos,0);
818
        ret[1] = mTmp[1]+computeAvg(curpos,1);
819
        ret[2] = mTmp[2]+computeAvg(curpos,2);
820

    
821
        return ret;
822
        }
823
      else
824
        {
825
        android.util.Log.e("D", "Error in getTrackingPoint: no outer face??");
826
        }
827
      }
828

    
829
    return null;
830
    }
831

    
832
///////////////////////////////////////////////////////////////////////////////////////////////////
833

    
834
  public int computeCurrentPuzzleFace(int type, float[] vertex)
835
    {
836
    if( type!=Cubit.TYPE_NORMAL )
837
      {
838
      Static3D[] axis = getFaceAxis();
839
      float[] dist3D = getDist3D(mNumLayers);
840
      final float MAXERR = 0.98f;
841
      int numAxis = axis.length;
842
      float x = vertex[0];
843
      float y = vertex[1];
844
      float z = vertex[2];
845

    
846
      for(int i=0; i<numAxis; i++)
847
        {
848
        Static3D ax = axis[i];
849
        float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
850
        if( len>mSize*dist3D[i]*MAXERR ) return i;
851
        }
852

    
853
      return -2;
854
      }
855

    
856
    return -1;
857
    }
858

    
859
///////////////////////////////////////////////////////////////////////////////////////////////////
860

    
861
  public float[] getCubitRowOffset(int cubitIndex)
862
    {
863
    return null;
864
    }
865

    
866
///////////////////////////////////////////////////////////////////////////////////////////////////
867

    
868
  void setRotationRowOffset(int puzzleFace, float[] offset)
869
    {
870
    mRowOffsets[puzzleFace][0] = offset[0];
871
    mRowOffsets[puzzleFace][1] = offset[1];
872
    mRowOffsets[puzzleFace][2] = offset[2];
873
    }
874

    
875
///////////////////////////////////////////////////////////////////////////////////////////////////
876

    
877
  int getNumAxis()
878
    {
879
    return mNumAxis;
880
    }
881

    
882
///////////////////////////////////////////////////////////////////////////////////////////////////
883

    
884
  public int[][] getSolvedQuats()
885
    {
886
    int[] groups = new int[mNumCubits];
887
    int numGroups = 1;
888
    int numFirst  = 0;
889

    
890
    for(int cubit=0; cubit<mNumCubits; cubit++)
891
      {
892
      groups[cubit] = retCubitSolvedStatus(cubit);
893
      if( groups[cubit]>=0 ) numGroups++;
894
      else                   numFirst++;
895
      }
896

    
897
    int firstIndex = 1;
898
    int groupIndex = 1;
899
    int[][] solvedQuats = new int[numGroups][];
900
    solvedQuats[0] = new int[1+numFirst];
901
    solvedQuats[0][0] = numFirst;
902
    Static3D[] axis = getFaceAxis();
903

    
904
    for(int cubit=0; cubit<mNumCubits; cubit++)
905
      {
906
      int group = groups[cubit];
907

    
908
      if( group<0 )
909
        {
910
        solvedQuats[0][firstIndex] = cubit;
911
        firstIndex++;
912
        }
913
      else
914
        {
915
        int[] quats = buildSolvedQuats(axis[group]);
916
        int len = quats==null ? 0 : quats.length;
917
        solvedQuats[groupIndex] = new int[2+len];
918
        solvedQuats[groupIndex][0] = 1;
919
        solvedQuats[groupIndex][1] = cubit;
920
        for(int i=0; i<len; i++) solvedQuats[groupIndex][i+2] = quats[i];
921
        groupIndex++;
922
        }
923
      }
924
/*
925
    String dbg = "SOLVED GROUPS:\n";
926

    
927
    for(int g=0; g<numGroups; g++)
928
      {
929
      int len = solvedQuats[g].length;
930
      for(int i=0; i<len; i++) dbg += (" "+solvedQuats[g][i]);
931
      dbg+="\n";
932
      }
933

    
934
    android.util.Log.e("D", dbg);
935
*/
936
    return solvedQuats;
937
    }
938

    
939
///////////////////////////////////////////////////////////////////////////////////////////////////
940

    
941
  public int getSolvedFunctionIndex()
942
    {
943
    return 0;
944
    }
945

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

    
951
  private boolean isSolvedCentersOnly()
952
    {
953
    int numGroups = mSolvedQuats.length;
954

    
955
    for(int group=1; group<numGroups; group++)
956
      {
957
      int numEntries= mSolvedQuats[group].length;
958
      int numCubits = mSolvedQuats[group][0];
959
      int firstCubit= mSolvedQuats[group][1];
960
      int firstQuat = mCubits[firstCubit].mQuatIndex;
961

    
962
      for(int cubit=2; cubit<=numCubits; cubit++)
963
        {
964
        int currCubit= mSolvedQuats[group][cubit];
965
        int currQuat = mCubits[currCubit].mQuatIndex;
966
        boolean isGood= (firstQuat==currQuat);
967

    
968
        for(int q=numCubits+1; !isGood && q<numEntries; q++)
969
          {
970
          int quat = mSolvedQuats[group][q];
971
          if( firstQuat == getMultQuat(currQuat,quat) ) isGood = true;
972
          }
973

    
974
        if( !isGood ) return false;
975
        }
976
      }
977

    
978
    return true;
979
    }
980

    
981
///////////////////////////////////////////////////////////////////////////////////////////////////
982

    
983
  private boolean isSolved0()
984
    {
985
    if( mSolvedQuats[0][0]==0 ) return isSolvedCentersOnly();
986

    
987
    for( int[] solvedQuat : mSolvedQuats )
988
      {
989
      int numCubits = solvedQuat[0];
990
      int firstCubit= solvedQuat[1];
991
      int quat = mCubits[firstCubit].mQuatIndex;
992

    
993
      for( int cubit=2; cubit<=numCubits; cubit++ )
994
        {
995
        int c = solvedQuat[cubit];
996
        if( quat != mCubits[c].mQuatIndex ) return false;
997
        }
998
      }
999

    
1000
    int cubit= mSolvedQuats[0][1];
1001
    int quat0= mCubits[cubit].mQuatIndex;
1002
    int numGroups = mSolvedQuats.length;
1003

    
1004
    for(int group=1; group<numGroups; group++)
1005
      {
1006
      int firstCubit= mSolvedQuats[group][1];
1007
      int currQuat  = mCubits[firstCubit].mQuatIndex;
1008

    
1009
      if( quat0==currQuat ) continue;
1010

    
1011
      boolean isGood= false;
1012
      int numEntries= mSolvedQuats[group].length;
1013
      int numCubits = mSolvedQuats[group][0];
1014

    
1015
      for(int q=numCubits+1; q<numEntries; q++)
1016
        {
1017
        int quat = mSolvedQuats[group][q];
1018

    
1019
        if( quat0 == getMultQuat(currQuat,quat) )
1020
          {
1021
          isGood = true;
1022
          break;
1023
          }
1024
        }
1025

    
1026
      if( !isGood ) return false;
1027
      }
1028

    
1029
    return true;
1030
    }
1031

    
1032
///////////////////////////////////////////////////////////////////////////////////////////////////
1033

    
1034
  private int computeScramble(int quatNum, int centerNum)
1035
    {
1036
    float MAXDIFF = 0.01f;
1037
    float[] center= mOrigPos[centerNum];
1038
    Static4D sc = new Static4D(center[0], center[1], center[2], 1.0f);
1039
    Static4D result = QuatHelper.rotateVectorByQuat(sc,mObjectQuats[quatNum]);
1040

    
1041
    float x = result.get0();
1042
    float y = result.get1();
1043
    float z = result.get2();
1044

    
1045
    for(int c=0; c<mNumCubits; c++)
1046
      {
1047
      float[] cent = mOrigPos[c];
1048

    
1049
      float qx = cent[0] - x;
1050
      float qy = cent[1] - y;
1051
      float qz = cent[2] - z;
1052

    
1053
      if( qx>-MAXDIFF && qx<MAXDIFF &&
1054
          qy>-MAXDIFF && qy<MAXDIFF &&
1055
          qz>-MAXDIFF && qz<MAXDIFF  ) return c;
1056
      }
1057

    
1058
    return -1;
1059
    }
1060

    
1061
///////////////////////////////////////////////////////////////////////////////////////////////////
1062
// Dino4 uses this. It is solved if and only if groups of cubits
1063
// (0,3,7), (1,2,5), (4,8,9), (6,10,11)
1064
// or
1065
// (0,1,4), (2,3,6), (5,9,10), (7,8,11)
1066
// are all the same color.
1067

    
1068
  private boolean isSolved1()
1069
    {
1070
    if( mScramble==null )
1071
      {
1072
      mScramble = new int[mNumQuats][mNumCubits];
1073
      mColors   = new int[mNumCubits];
1074

    
1075
      for(int q=0; q<mNumQuats; q++)
1076
        for(int c=0; c<mNumCubits; c++) mScramble[q][c] = computeScramble(q,c);
1077
      }
1078

    
1079
    if( mFaceMap==null )
1080
      {
1081
      mFaceMap = new int[] { 4, 2, 2, 4, 0, 2, 1, 4, 0, 0, 1, 1 };
1082
      }
1083

    
1084
    for(int c=0; c<mNumCubits; c++)
1085
      {
1086
      int index = mScramble[mCubits[c].mQuatIndex][c];
1087
      mColors[index] = mFaceMap[c];
1088
      }
1089

    
1090
    if( mColors[0]==mColors[3] && mColors[0]==mColors[7] &&
1091
        mColors[1]==mColors[2] && mColors[1]==mColors[5] &&
1092
        mColors[4]==mColors[8] && mColors[4]==mColors[9]  ) return true;
1093

    
1094
    if( mColors[0]==mColors[1] && mColors[0]==mColors[4] &&
1095
        mColors[2]==mColors[3] && mColors[2]==mColors[6] &&
1096
        mColors[5]==mColors[9] && mColors[5]==mColors[10] ) return true;
1097

    
1098
    return false;
1099
    }
1100

    
1101
///////////////////////////////////////////////////////////////////////////////////////////////////
1102

    
1103
  int computeRow(float[] pos, int axisIndex, int cubitType, int puzzleFace)
1104
    {
1105
    int ret=0;
1106
    int len = pos.length / 3;
1107
    Static3D axis = mAxis[axisIndex];
1108
    float axisX = axis.get0();
1109
    float axisY = axis.get1();
1110
    float axisZ = axis.get2();
1111
    float casted, xoff=0, yoff=0, zoff=0;
1112

    
1113
    if( cubitType!=Cubit.TYPE_NORMAL )
1114
      {
1115
      xoff = mRowOffsets[puzzleFace][0];
1116
      yoff = mRowOffsets[puzzleFace][1];
1117
      zoff = mRowOffsets[puzzleFace][2];
1118
      }
1119

    
1120
    for(int i=0; i<len; i++)
1121
      {
1122
      casted = (pos[3*i]+xoff)*axisX + (pos[3*i+1]+yoff)*axisY + (pos[3*i+2]+zoff)*axisZ;
1123
      ret |= computeSingleRow(axisIndex,casted);
1124
      }
1125

    
1126
    return ret;
1127
    }
1128

    
1129
///////////////////////////////////////////////////////////////////////////////////////////////////
1130

    
1131
  private int computeSingleRow(int axisIndex,float casted)
1132
    {
1133
    int num = mNumCuts[axisIndex];
1134

    
1135
    for(int i=0; i<num; i++)
1136
      {
1137
      if( casted<mCuts[axisIndex][i] ) return (1<<i);
1138
      }
1139

    
1140
    return (1<<num);
1141
    }
1142

    
1143
///////////////////////////////////////////////////////////////////////////////////////////////////
1144

    
1145
  private boolean wasRotateApplied()
1146
    {
1147
    return mEffects.exists(mRotateEffect.getID());
1148
    }
1149

    
1150
///////////////////////////////////////////////////////////////////////////////////////////////////
1151

    
1152
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
1153
    {
1154
    return (mCubits[cubit].getRotRow(axis) & rowBitmap) != 0;
1155
    }
1156

    
1157
///////////////////////////////////////////////////////////////////////////////////////////////////
1158
// note the minus in front of the sin() - we rotate counterclockwise
1159
// when looking towards the direction where the axis increases in values.
1160

    
1161
  private Static4D makeQuaternion(float axisX, float axisY, float axisZ, int angleInDegrees)
1162
    {
1163
    while( angleInDegrees<0 ) angleInDegrees += 360;
1164
    angleInDegrees %= 360;
1165
    
1166
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
1167
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
1168

    
1169
    return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
1170
    }
1171

    
1172
///////////////////////////////////////////////////////////////////////////////////////////////////
1173

    
1174
  private synchronized void setupPosition(int[][] moves)
1175
    {
1176
    if( moves!=null )
1177
      {
1178
      Static4D quat;
1179
      int index, axisIndex, row, rowBitmap, basic, angle;
1180

    
1181
      for(int[] move: moves)
1182
        {
1183
        axisIndex= move[0];
1184
        rowBitmap= computeBitmapFromRow( move[1],axisIndex);
1185
        row      = computeRowFromBitmap( move[1] );
1186
        basic    = mBasicAngles[axisIndex][row];
1187
        angle    = move[2]*(360/basic);   // this assumes that all layers from
1188
                                          // the bitmap have the same BasicAngle.
1189
                                          // at the moment this is always true as
1190
                                          // there are no bandaged objects with
1191
                                          // different per-layer BasicAngles.
1192
        Static3D axis = mAxis[axisIndex];
1193
        float axisX = axis.get0();
1194
        float axisY = axis.get1();
1195
        float axisZ = axis.get2();
1196
        quat = makeQuaternion(axisX,axisY,axisZ,angle);
1197

    
1198
        for(int i=0; i<mNumCubits; i++)
1199
          {
1200
          mBelongs[i] = belongsToRotation(i,axisIndex,rowBitmap);
1201
          if( mBelongs[i] )
1202
            {
1203
            boolean result = mCubits[i].rotateCubit(quat);
1204
            if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,1);
1205
            }
1206
          }
1207

    
1208
        recomputeFaceOffsets();
1209

    
1210
        for(int i=0; i<mNumCubits; i++)
1211
          {
1212
          if( mBelongs[i] )
1213
            {
1214
            index = mCubits[i].postRotateCubit(quat);
1215
            setCubitQuat(i,mCubits[i].computeAssociation(),index);
1216
            }
1217
          else if( mCubits[i].getType()==Cubit.TYPE_FOLLOWER )
1218
            {
1219
            mCubits[i].computeRotationRow();
1220
            setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
1221
            }
1222
          }
1223
        }
1224
      }
1225
    }
1226

    
1227
///////////////////////////////////////////////////////////////////////////////////////////////////
1228

    
1229
  public int getScrambleType()
1230
    {
1231
    return 0;
1232
    }
1233

    
1234
///////////////////////////////////////////////////////////////////////////////////////////////////
1235

    
1236
  int computeBitmapFromRow(int rowBitmap, int axis)
1237
    {
1238
    if( mIsBandaged )
1239
      {
1240
      int bitmap, initBitmap=0;
1241

    
1242
      while( initBitmap!=rowBitmap )
1243
        {
1244
        initBitmap = rowBitmap;
1245

    
1246
        for(int cubit=0; cubit<mNumCubits; cubit++)
1247
          {
1248
          bitmap = mCubits[cubit].getRotRow(axis);
1249
          if( (rowBitmap & bitmap) != 0 ) rowBitmap |= bitmap;
1250
          }
1251
        }
1252
      }
1253

    
1254
    return rowBitmap;
1255
    }
1256

    
1257
///////////////////////////////////////////////////////////////////////////////////////////////////
1258

    
1259
  private int computeRowFromBitmap(int rowBitmap)
1260
    {
1261
    int index = 0;
1262

    
1263
    while(index<32)
1264
      {
1265
      if( (rowBitmap&0x1) != 0 ) return index;
1266
      rowBitmap>>=1;
1267
      index++;
1268
      }
1269
    return 0;
1270
    }
1271

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

    
1276
  void clampPos(float[] pos, int offset)
1277
    {
1278
    float currError, minError = Float.MAX_VALUE;
1279
    int minErrorIndex1 = -1;
1280
    int minErrorIndex2 = -1;
1281

    
1282
    float x = pos[offset  ];
1283
    float y = pos[offset+1];
1284
    float z = pos[offset+2];
1285

    
1286
    float xo,yo,zo;
1287

    
1288
    for(int i=0; i<mNumCubits; i++)
1289
      {
1290
      int len = mOrigPos[i].length / 3;
1291

    
1292
      for(int j=0; j<len; j++)
1293
        {
1294
        xo = mOrigPos[i][3*j  ];
1295
        yo = mOrigPos[i][3*j+1];
1296
        zo = mOrigPos[i][3*j+2];
1297

    
1298
        currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
1299

    
1300
        if( currError<minError )
1301
          {
1302
          minError = currError;
1303
          minErrorIndex1 = i;
1304
          minErrorIndex2 = j;
1305
          }
1306
        }
1307
      }
1308

    
1309
    if( minError< 0.05f ) // TODO: 0.05 ?
1310
      {
1311
      pos[offset  ] = mOrigPos[minErrorIndex1][3*minErrorIndex2  ];
1312
      pos[offset+1] = mOrigPos[minErrorIndex1][3*minErrorIndex2+1];
1313
      pos[offset+2] = mOrigPos[minErrorIndex1][3*minErrorIndex2+2];
1314
      }
1315
    }
1316

    
1317
///////////////////////////////////////////////////////////////////////////////////////////////////
1318
// remember about the double cover or unit quaternions!
1319

    
1320
  int mulQuat(int q1, int q2)
1321
    {
1322
    Static4D result = QuatHelper.quatMultiply(mObjectQuats[q1],mObjectQuats[q2]);
1323

    
1324
    float rX = result.get0();
1325
    float rY = result.get1();
1326
    float rZ = result.get2();
1327
    float rW = result.get3();
1328

    
1329
    final float MAX_ERROR = 0.1f;
1330
    float dX,dY,dZ,dW;
1331

    
1332
    for(int i=0; i<mNumQuats; i++)
1333
      {
1334
      dX = mObjectQuats[i].get0() - rX;
1335
      dY = mObjectQuats[i].get1() - rY;
1336
      dZ = mObjectQuats[i].get2() - rZ;
1337
      dW = mObjectQuats[i].get3() - rW;
1338

    
1339
      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
1340
          dY<MAX_ERROR && dY>-MAX_ERROR &&
1341
          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
1342
          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
1343

    
1344
      dX = mObjectQuats[i].get0() + rX;
1345
      dY = mObjectQuats[i].get1() + rY;
1346
      dZ = mObjectQuats[i].get2() + rZ;
1347
      dW = mObjectQuats[i].get3() + rW;
1348

    
1349
      if( dX<MAX_ERROR && dX>-MAX_ERROR &&
1350
          dY<MAX_ERROR && dY>-MAX_ERROR &&
1351
          dZ<MAX_ERROR && dZ>-MAX_ERROR &&
1352
          dW<MAX_ERROR && dW>-MAX_ERROR  ) return i;
1353
      }
1354

    
1355
    return -1;
1356
    }
1357

    
1358
///////////////////////////////////////////////////////////////////////////////////////////////////
1359

    
1360
  private float getAngle()
1361
    {
1362
    int pointNum = mRotationAngle.getNumPoints();
1363

    
1364
    if( pointNum>=1 )
1365
      {
1366
      return mRotationAngle.getPoint(pointNum-1).get0();
1367
      }
1368
    else
1369
      {
1370
      mInterface.reportProblem("points in RotationAngle: "+pointNum, false);
1371
      return 0;
1372
      }
1373
    }
1374

    
1375
///////////////////////////////////////////////////////////////////////////////////////////////////
1376

    
1377
  void setLibInterface(ObjectLibInterface inter)
1378
    {
1379
    mInterface = inter;
1380
    }
1381

    
1382
///////////////////////////////////////////////////////////////////////////////////////////////////
1383

    
1384
  void applyScrambles(int[][] moves)
1385
    {
1386
    setupPosition(moves);
1387
    }
1388

    
1389
///////////////////////////////////////////////////////////////////////////////////////////////////
1390

    
1391
  void initializeObject(int[][] moves)
1392
    {
1393
    solve();
1394
    setupPosition(moves);
1395
    }
1396

    
1397
///////////////////////////////////////////////////////////////////////////////////////////////////
1398

    
1399
  synchronized void removeRotationNow()
1400
    {
1401
    float angle = getAngle();
1402
    double nearestAngleInRadians = angle*Math.PI/180;
1403
    float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
1404
    float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
1405
    float axisX = mAxis[mCurrentRotAxis].get0();
1406
    float axisY = mAxis[mCurrentRotAxis].get1();
1407
    float axisZ = mAxis[mCurrentRotAxis].get2();
1408
    Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
1409

    
1410
    mRotationAngle.removeAll();
1411
    mRotationAngleStatic.set0(0);
1412

    
1413
    for(int i=0; i<mNumCubits; i++)
1414
      {
1415
      mBelongs[i] = belongsToRotation(i, mCurrentRotAxis,mRotRowBitmap);
1416
      if( mBelongs[i] )
1417
        {
1418
        boolean result = mCubits[i].rotateCubit(quat);
1419
        if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,2);
1420
        }
1421
      }
1422

    
1423
    recomputeFaceOffsets();
1424

    
1425
    for(int i=0; i<mNumCubits; i++)
1426
      {
1427
      if( mBelongs[i] )
1428
        {
1429
        int index = mCubits[i].postRotateCubit(quat);
1430
        setCubitQuat(i,mCubits[i].computeAssociation(),index);
1431
        }
1432
      else if( mCubits[i].getType()==Cubit.TYPE_FOLLOWER )
1433
        {
1434
        mCubits[i].computeRotationRow();
1435
        setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
1436
        }
1437
      }
1438
    }
1439

    
1440
///////////////////////////////////////////////////////////////////////////////////////////////////
1441

    
1442
  private void recomputeFaceOffsets()
1443
    {
1444
    for(int i=0; i<mNumPuzzleFaces; i++)
1445
      {
1446
      mRowOffsets[i][0] =0;
1447
      mRowOffsets[i][1] =0;
1448
      mRowOffsets[i][2] =0;
1449
      }
1450

    
1451
    for(int i=0; i<mNumCubits; i++)
1452
      if( mCubits[i].getType()==Cubit.TYPE_DECIDER )
1453
        {
1454
        float[] offset = mCubits[i].getOffset();
1455
        int face = mCubits[i].getPuzzleFace();
1456
        mRowOffsets[face][0] = offset[0];
1457
        mRowOffsets[face][1] = offset[1];
1458
        mRowOffsets[face][2] = offset[2];
1459
        }
1460
    }
1461

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

    
1464
  long finishRotationNow(EffectListener listener, int nearestAngleInDegrees)
1465
    {
1466
    if( wasRotateApplied() )
1467
      {
1468
      float angle = getAngle();
1469
      mRotationAngleStatic.set0(angle);
1470
      mRotationAngleFinal.set0(nearestAngleInDegrees);
1471
      mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
1472

    
1473
      mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
1474
      mRotationAngle.resetToBeginning();
1475
      mRotationAngle.removeAll();
1476
      mRotationAngle.add(mRotationAngleStatic);
1477
      mRotationAngle.add(mRotationAngleMiddle);
1478
      mRotationAngle.add(mRotationAngleFinal);
1479
      mRotateEffect.notifyWhenFinished(listener);
1480

    
1481
      return mRotateEffect.getID();
1482
      }
1483

    
1484
    return 0;
1485
    }
1486

    
1487
///////////////////////////////////////////////////////////////////////////////////////////////////
1488

    
1489
  synchronized long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
1490
    {
1491
    if( wasRotateApplied() )
1492
      {
1493
      mCurrentRotAxis = axis;
1494
      mRotRowBitmap= computeBitmapFromRow( rowBitmap,axis );
1495

    
1496
      mRotationAngleStatic.set0(0.0f);
1497
      mRotationAxis.set( mAxis[axis] );
1498
      mRotationAngle.setDuration(durationMillis);
1499
      mRotationAngle.resetToBeginning();
1500
      mRotationAngle.add(new Static1D(0));
1501
      mRotationAngle.add(new Static1D(angle));
1502
      mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1503
      mRotateEffect.notifyWhenFinished(listener);
1504

    
1505
      return mRotateEffect.getID();
1506
      }
1507

    
1508
    return 0;
1509
    }
1510

    
1511
///////////////////////////////////////////////////////////////////////////////////////////////////
1512

    
1513
  void continueRotation(float angleInDegrees)
1514
    {
1515
    mRotationAngleStatic.set0(angleInDegrees);
1516
    }
1517

    
1518
///////////////////////////////////////////////////////////////////////////////////////////////////
1519

    
1520
  synchronized void beginNewRotation(int axis, int row )
1521
    {
1522
    if( axis<0 || axis>=mNumAxis )
1523
      {
1524
      android.util.Log.e("object", "invalid rotation axis: "+axis);
1525
      return;
1526
      }
1527
    if( row<0 || row>=mNumLayers[axis] )
1528
      {
1529
      android.util.Log.e("object", "invalid rotation row: "+row);
1530
      return;
1531
      }
1532

    
1533
    mCurrentRotAxis = axis;
1534
    mRotRowBitmap= computeBitmapFromRow( (1<<row),axis );
1535
    mRotationAngleStatic.set0(0.0f);
1536
    mRotationAxis.set( mAxis[axis] );
1537
    mRotationAngle.add(mRotationAngleStatic);
1538
    mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1539
    }
1540

    
1541
///////////////////////////////////////////////////////////////////////////////////////////////////
1542

    
1543
  void setTextureMap(int cubit, int face, int color)
1544
    {
1545
    int variant  = getCubitVariant(cubit,mNumLayers);
1546
    int shape    = getVariantStickerShape(variant,face);
1547
    int texIndex = color<0 || shape<0 ? mNumTextures-mNumOverrides : shape*mNumFaceColors + color;
1548
    int row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1549
    int col      = texIndex%mNumTexCols;
1550

    
1551
    final float ratioW = 1.0f/mNumTexCols;
1552
    final float ratioH = 1.0f/mNumTexRows;
1553
    final Static4D[] maps = new Static4D[1];
1554
    maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1555
    mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1556
    }
1557

    
1558
///////////////////////////////////////////////////////////////////////////////////////////////////
1559

    
1560
  private int getCubitFaceColor(int cubit, int face)
1561
    {
1562
    int puzzleFace = getCubitFaceMap(cubit,face);
1563
    if( puzzleFace>=0 ) puzzleFace %= mNumFaceColors;
1564
    return puzzleFace;
1565
    }
1566

    
1567
///////////////////////////////////////////////////////////////////////////////////////////////////
1568

    
1569
  public int getCubitFaceMap(int cubit, int face)
1570
    {
1571
    int numFaces = mCubitFaceColors[cubit].length;
1572
    int puzzleFace = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
1573
    return puzzleFace<0 ? -1 : puzzleFace;
1574
    }
1575

    
1576
///////////////////////////////////////////////////////////////////////////////////////////////////
1577

    
1578
  void resetAllTextureMaps()
1579
    {
1580
    final float ratioW = 1.0f/mNumTexCols;
1581
    final float ratioH = 1.0f/mNumTexRows;
1582
    int cubColor, stiShape, texIndex, variant, row, col;
1583

    
1584
    for(int cubit=0; cubit<mNumCubits; cubit++)
1585
      {
1586
      final Static4D[] maps = new Static4D[mNumCubitFaces];
1587
      variant = getCubitVariant(cubit,mNumLayers);
1588

    
1589
      for(int face=0; face<mNumCubitFaces; face++)
1590
        {
1591
        cubColor = getCubitFaceColor(cubit,face);
1592
        stiShape = getVariantStickerShape(variant,face);
1593
        texIndex = cubColor<0 || stiShape<0 ? mNumTextures-mNumOverrides : stiShape*mNumFaceColors + cubColor;
1594
        row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1595
        col      = texIndex%mNumTexCols;
1596

    
1597
        maps[face] = new Static4D( col*ratioW, row*ratioH, ratioW, ratioH);
1598
        }
1599

    
1600
      mMesh.setTextureMap(maps,mNumCubitFaces*cubit);
1601
      }
1602

    
1603
    overrideCubitFaceColor();
1604
    }
1605

    
1606
///////////////////////////////////////////////////////////////////////////////////////////////////
1607

    
1608
  private void overrideCubitFaceColor()
1609
    {
1610
    final float ratioW = 1.0f/mNumTexCols;
1611
    final float ratioH = 1.0f/mNumTexRows;
1612

    
1613
    for(int i=0; i<mNumOverrides; i++)
1614
      {
1615
      int[] cubitFaces = mStickerOverrides[i].getCubitFaces();
1616
      int length = cubitFaces.length/2;
1617

    
1618
      for(int j=0; j<length; j++)
1619
        {
1620
        final Static4D[] maps = new Static4D[1];
1621
        int color = mNumTextures-mNumOverrides+1+i;
1622
        int row   = (mNumTexRows-1) - color/mNumTexCols;
1623
        int col   = color%mNumTexCols;
1624
        int cubit = cubitFaces[2*j];
1625
        int face  = cubitFaces[2*j+1];
1626
        maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1627
        mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1628
        }
1629
      }
1630
    }
1631

    
1632
///////////////////////////////////////////////////////////////////////////////////////////////////
1633

    
1634
  void releaseResources()
1635
    {
1636
    mTexture.markForDeletion();
1637
    mMesh.markForDeletion();
1638
    mEffects.markForDeletion();
1639

    
1640
    for(int j=0; j<mNumCubits; j++)
1641
      {
1642
      mCubits[j].releaseResources();
1643
      }
1644
    }
1645

    
1646
///////////////////////////////////////////////////////////////////////////////////////////////////
1647

    
1648
  private void setCubitQuat(int cubit, int andAssociation, int equAssociation)
1649
    {
1650
    if( !mIsInMixupMode )
1651
      {
1652
      mMesh.setEffectAssociation(cubit,andAssociation,equAssociation);
1653
      }
1654
    else
1655
      {
1656
      mMesh.setEffectAssociation(cubit,andAssociation,cubit);
1657
      Static4D tmp = mObjectQuats[equAssociation];
1658
      mMixupModeQuats[cubit].set(tmp);
1659
      }
1660
    }
1661

    
1662
///////////////////////////////////////////////////////////////////////////////////////////////////
1663

    
1664
  synchronized void restorePreferences(SharedPreferences preferences)
1665
    {
1666
    boolean error = false;
1667
    String key = getShortName();
1668

    
1669
    for(int i=0; i<mNumCubits; i++)
1670
      {
1671
      mQuatDebug[i] = mCubits[i].restorePreferences(key,preferences);
1672

    
1673
      if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1674
        {
1675
        boolean result = mCubits[i].rotateCubit(mObjectQuats[mQuatDebug[i]]);
1676
        if( !result ) debugQuat(mObjectQuats[mQuatDebug[i]],i,0,0,0,mQuatDebug[i],3);
1677
        }
1678
      else { error = true; break; }
1679
      }
1680

    
1681
    if( !error )
1682
      {
1683
      recomputeFaceOffsets();
1684

    
1685
      for(int i=0; i<mNumCubits; i++)
1686
        {
1687
        if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1688
          {
1689
          mCubits[i].computeRotationRow();
1690
          setCubitQuat(i,mCubits[i].computeAssociation(),mQuatDebug[i]);
1691
          }
1692
        else { error = true; break; }
1693
        }
1694
      }
1695

    
1696
    if( error )
1697
      {
1698
      for(int i=0; i<mNumCubits; i++)
1699
        {
1700
        mCubits[i].solve();
1701
        setCubitQuat(i,mCubits[i].computeAssociation(),0);
1702
        }
1703
      }
1704
    }
1705

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

    
1708
  void savePreferences(SharedPreferences.Editor editor)
1709
    {
1710
    String key = getShortName();
1711
    for(int i=0; i<mNumCubits; i++) mCubits[i].savePreferences(key,editor);
1712
    }
1713

    
1714
///////////////////////////////////////////////////////////////////////////////////////////////////
1715

    
1716
  public void removePreferences(SharedPreferences.Editor editor)
1717
    {
1718
    String key = getShortName();
1719
    for(int i=0; i<mNumCubits; i++) mCubits[i].removePreferences(key,editor);
1720
    }
1721

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

    
1724
  private float computeRadiusCorrection(float[] sticker, int curr, int len)
1725
    {
1726
    final float A = 0.8f;  // 0<A<1
1727

    
1728
    int prev = curr>0 ? curr-1 : len-1;
1729
    int next = curr<len-1 ? curr+1 : 0;
1730

    
1731
    float v1x = sticker[2*prev  ]-sticker[2*curr  ];
1732
    float v1y = sticker[2*prev+1]-sticker[2*curr+1];
1733
    float v2x = sticker[2*next  ]-sticker[2*curr  ];
1734
    float v2y = sticker[2*next+1]-sticker[2*curr+1];
1735

    
1736
    float len1= v1x*v1x+v1y*v1y;
1737
    float len2= v2x*v2x+v2y*v2y;
1738

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

    
1741
    return 1-A*cos;
1742
    }
1743

    
1744
///////////////////////////////////////////////////////////////////////////////////////////////////
1745
// Radius of the sphere circumscribed on the puzzle. Needed for pillowing.
1746
//
1747
// This won't work correctly for pillowing off-center puzzles (e.g. mirrors) - for those we'd need
1748
// to introduce the concept of a 'sink center' as well.
1749
//
1750
// public because needed in TouchControlShapemod
1751

    
1752
  public float getCircumscribedRadius()
1753
    {
1754
    switch(mNumPuzzleFaces)
1755
      {
1756
      case  4: return (SQ6/4)*mSize;
1757
      case  6: return (SQ3/2)*mSize;
1758
      case  8: return (SQ2/2)*mSize;
1759
      case 12: return (SQ3/2)*((SQ5+1)/2)*mSize;
1760
      case 16: return 0.50f*mSize;
1761
      }
1762

    
1763
    return 0.0f;
1764
    }
1765

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

    
1768
  public ObjectSticker retSticker(int sticker)
1769
    {
1770
    if( mStickers==null )
1771
      {
1772
      float rad = getStickerRadius();
1773
      float str = getStickerStroke();
1774
      float[][] angles = getStickerAngles();
1775
      int numStickers = mStickerCoords.length;
1776
      mStickers = new ObjectSticker[numStickers];
1777

    
1778
      for(int s=0; s<numStickers; s++)
1779
        {
1780
        float scale = mStickerScales.length>s ? mStickerScales[s] : 1.0f;
1781
        float radius = rad / scale;
1782
        float stroke = str / scale;
1783
        int len = mStickerCoords[s].length/2;
1784
        float[] radii = new float[len];
1785
        for(int r=0; r<len; r++) radii[r] = radius*computeRadiusCorrection(mStickerCoords[s],r,len);
1786
        mStickers[s] = new ObjectSticker(mStickerCoords[s],angles==null ? null : angles[s],radii,stroke);
1787
        }
1788
      }
1789

    
1790
    return mStickers[sticker];
1791
    }
1792

    
1793
///////////////////////////////////////////////////////////////////////////////////////////////////
1794
// some objects (currently Kilominx,Ivy,Rex) might want to change the stickers.
1795

    
1796
  public void adjustStickerCoords()
1797
    {
1798

    
1799
    }
1800

    
1801
///////////////////////////////////////////////////////////////////////////////////////////////////
1802

    
1803
  public Static4D[] getQuats()
1804
    {
1805
    if( mObjectQuats==null )
1806
      {
1807
      mObjectQuats = QuatGroupGenerator.computeGroup(mAxis,mBasicAngles);
1808
      }
1809

    
1810
    return mObjectQuats;
1811
    }
1812

    
1813
///////////////////////////////////////////////////////////////////////////////////////////////////
1814

    
1815
  public int[][] getVariantFaceIsOuter()
1816
    {
1817
    return mVariantFaceIsOuter;
1818
    }
1819

    
1820
///////////////////////////////////////////////////////////////////////////////////////////////////
1821

    
1822
  public int getInternalColor()
1823
    {
1824
    return COLOR_INTERNAL;
1825
    }
1826

    
1827
///////////////////////////////////////////////////////////////////////////////////////////////////
1828
// the getFaceColors + final INTERNAL_COLOR in a grid (so that we do not exceed the maximum texture size)
1829

    
1830
  private void createTexture()
1831
    {
1832
    Paint paint = new Paint();
1833
    mBitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_4444);
1834
    Canvas canvas = new Canvas(mBitmap);
1835

    
1836
    paint.setAntiAlias(true);
1837
    paint.setTextAlign(Paint.Align.CENTER);
1838
    paint.setStyle(Paint.Style.FILL);
1839
    paint.setColor(getInternalColor());
1840
    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
1841

    
1842
    int texture = 0;
1843
    FactorySticker factory = FactorySticker.getInstance();
1844

    
1845
    for(int row=0; row<mNumTexRows; row++)
1846
      for(int col=0; col<mNumTexCols; col++)
1847
        {
1848
        if( texture<mNumTextures-mNumOverrides )
1849
          {
1850
          ObjectSticker sticker = retSticker(texture/mNumFaceColors);
1851
          int color = getColor(texture%mNumFaceColors);
1852
          factory.drawRoundedPolygon(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color, sticker);
1853
          }
1854
        else if( texture>mNumTextures-mNumOverrides && texture<=mNumTextures )
1855
          {
1856
          int color = mStickerOverrides[mNumTextures-texture].getColor();
1857
          factory.drawSolidColor(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color);
1858
          }
1859

    
1860
        texture++;
1861
        }
1862
    }
1863

    
1864
///////////////////////////////////////////////////////////////////////////////////////////////////
1865

    
1866
  void setTexture()
1867
    {
1868
    if( mBitmap==null ) createTexture();
1869

    
1870
    if( !mTexture.setTextureAlreadyInverted(mBitmap) )
1871
      {
1872
      int max = DistortedLibrary.getMaxTextureSize();
1873
      mInterface.reportProblem("failed to set texture of size "+mBitmap.getWidth()+"x"+mBitmap.getHeight()+" max is "+max, true);
1874
      }
1875
    }
1876

    
1877
///////////////////////////////////////////////////////////////////////////////////////////////////
1878

    
1879
  void setObjectRatioNow(float sc, int nodeSize)
1880
    {
1881
    mObjectScreenRatio = sc;
1882
    float scale = mObjectScreenRatio*mInitScreenRatio*nodeSize/mSize;
1883
    mObjectScale.set(scale,scale,scale);
1884

    
1885
    if( mTouchControl ==null ) mTouchControl = getTouchControl();
1886
    mTouchControl.setObjectRatio(mObjectScreenRatio*mInitScreenRatio);
1887
    }
1888

    
1889
///////////////////////////////////////////////////////////////////////////////////////////////////
1890

    
1891
  void setObjectRatio(float sizeChange, int nodeSize)
1892
    {
1893
    mObjectScreenRatio *= (1.0f+sizeChange)/2;
1894

    
1895
    if( mObjectScreenRatio>MAX_SIZE_CHANGE) mObjectScreenRatio = MAX_SIZE_CHANGE;
1896
    if( mObjectScreenRatio<MIN_SIZE_CHANGE) mObjectScreenRatio = MIN_SIZE_CHANGE;
1897

    
1898
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1899
    }
1900

    
1901
///////////////////////////////////////////////////////////////////////////////////////////////////
1902

    
1903
  void setNodeSize(int nodeSize)
1904
    {
1905
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1906
    }
1907

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

    
1910
  public float getRatio()
1911
    {
1912
    return mObjectScreenRatio;
1913
    }
1914

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

    
1917
  public float getObjectRatio()
1918
    {
1919
    return mObjectScreenRatio*mInitScreenRatio;
1920
    }
1921

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

    
1924
  boolean isSolved()
1925
    {
1926
    if( mSolvedFunctionIndex==0 ) return isSolved0();
1927
    if( mSolvedFunctionIndex==1 ) return isSolved1();
1928

    
1929
    return false;
1930
    }
1931

    
1932
///////////////////////////////////////////////////////////////////////////////////////////////////
1933

    
1934
  int computeNearestAngle(int basicAngle, float angle, float speed)
1935
    {
1936
    int nearestAngle = 360/basicAngle;
1937
    int tmp = (int)((angle+nearestAngle/2)/nearestAngle);
1938
    if( angle< -(nearestAngle*0.5) ) tmp-=1;
1939

    
1940
    if( tmp!=0 ) return nearestAngle*tmp;
1941

    
1942
    return speed> 1.2f ? nearestAngle*(angle>0 ? 1:-1) : 0;
1943
    }
1944

    
1945
///////////////////////////////////////////////////////////////////////////////////////////////////
1946
// INTERNAL API - those are called from 'effects' package
1947
///////////////////////////////////////////////////////////////////////////////////////////////////
1948

    
1949
  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
1950
    {
1951
    mScrambler.randomizeNewScramble(scramble,rnd,curr,total, getSignature() );
1952
    }
1953

    
1954
///////////////////////////////////////////////////////////////////////////////////////////////////
1955

    
1956
  public Static4D getRotationQuat()
1957
    {
1958
    return mQuat;
1959
    }
1960

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

    
1963
  public float getSize()
1964
    {
1965
    return mSize;
1966
    }
1967

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

    
1970
  public void applyEffect(Effect effect, int position)
1971
    {
1972
    mEffects.apply(effect, position);
1973
    }
1974

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

    
1977
  public void removeEffect(long effectID)
1978
    {
1979
    mEffects.abortById(effectID);
1980
    }
1981

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

    
1984
  public MeshBase getObjectMesh()
1985
    {
1986
    return mMesh;
1987
    }
1988

    
1989
///////////////////////////////////////////////////////////////////////////////////////////////////
1990

    
1991
  public DistortedEffects getObjectEffects()
1992
    {
1993
    return mEffects;
1994
    }
1995

    
1996
///////////////////////////////////////////////////////////////////////////////////////////////////
1997

    
1998
  public int getCubitType(int cubit)
1999
    {
2000
    return mCubits[cubit].getType();
2001
    }
2002

    
2003
///////////////////////////////////////////////////////////////////////////////////////////////////
2004

    
2005
  public float[] getCubitOffset(int cubit)
2006
    {
2007
    return mCubits[cubit].getOffset();
2008
    }
2009

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

    
2012
  public ObjectStickerOverride[] getStickerOverrides()
2013
    {
2014
    return null;
2015
    }
2016

    
2017
///////////////////////////////////////////////////////////////////////////////////////////////////
2018

    
2019
  public boolean getError()
2020
    {
2021
    return mError;
2022
    }
2023

    
2024
///////////////////////////////////////////////////////////////////////////////////////////////////
2025

    
2026
  public String getErrorString()
2027
    {
2028
    return mErrorString;
2029
    }
2030

    
2031
///////////////////////////////////////////////////////////////////////////////////////////////////
2032
// PUBLIC API
2033
///////////////////////////////////////////////////////////////////////////////////////////////////
2034

    
2035
  public int getCubitFaceColorIndex(int cubit, int face)
2036
    {
2037
    Static4D texMap = mMesh.getTextureMap(mNumFaceColors *cubit + face);
2038

    
2039
    int x = (int)(texMap.get0()/texMap.get2());
2040
    int y = (int)(texMap.get1()/texMap.get3());
2041

    
2042
    return (mNumTexRows-1-y)*NUM_STICKERS_IN_ROW + x;
2043
    }
2044

    
2045
///////////////////////////////////////////////////////////////////////////////////////////////////
2046

    
2047
  public int[] getNumLayers()
2048
    {
2049
    return mNumLayers;
2050
    }
2051

    
2052
///////////////////////////////////////////////////////////////////////////////////////////////////
2053

    
2054
  public synchronized void solve()
2055
    {
2056
    for(int i=0; i<mNumCubits; i++)
2057
      {
2058
      mCubits[i].solve();
2059
      }
2060

    
2061
    recomputeFaceOffsets();
2062

    
2063
    for(int i=0; i<mNumCubits; i++)
2064
      {
2065
      mCubits[i].computeRotationRow();
2066
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
2067
      }
2068
    }
2069

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

    
2072
  public int getCubitQuatIndex(int cubit)
2073
    {
2074
    return (cubit>=0 && cubit<mNumCubits) ? mCubits[cubit].mQuatIndex : 0;
2075
    }
2076

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

    
2079
  public int getCubitRotRow(int cubit, int axis)
2080
    {
2081
    return mCubits[cubit].getRotRow(axis);
2082
    }
2083

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

    
2086
  public Bitmap getStickerBitmap()
2087
    {
2088
    return mBitmap;
2089
    }
2090

    
2091
///////////////////////////////////////////////////////////////////////////////////////////////////
2092

    
2093
  public DistortedNode getNode()
2094
    {
2095
    return mNode;
2096
    }
2097

    
2098
///////////////////////////////////////////////////////////////////////////////////////////////////
2099

    
2100
  public int getNumStickerTypes()
2101
    {
2102
    return mNumStickerTypes;
2103
    }
2104

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

    
2107
  public String reportState()
2108
    {
2109
    StringBuilder builder = new StringBuilder();
2110

    
2111
    for(int i=0; i<mNumCubits; i++ )
2112
      {
2113
      if( i>0 ) builder.append('.');
2114
      builder.append(mCubits[i].mQuatIndex);
2115
      }
2116

    
2117
    return builder.toString();
2118
    }
2119

    
2120
///////////////////////////////////////////////////////////////////////////////////////////////////
2121
// this is here only so it can be overridden in TwistyJSON so that we can get this from JSON.
2122

    
2123
  public int getNumCubitFaces()
2124
    {
2125
    return 0;
2126
    }
2127

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

    
2133
  public float getPillowCoeff()
2134
    {
2135
    return 1.0f;
2136
    }
2137

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

    
2140
  public TouchControl getTouchControl()
2141
    {
2142
    if( mTouchControl==null )
2143
      {
2144
      switch(getTouchControlType())
2145
        {
2146
        case TC_TETRAHEDRON      : mTouchControl = new TouchControlTetrahedron(this);
2147
                                   break;
2148
        case TC_HEXAHEDRON       : mTouchControl = new TouchControlHexahedron(this);
2149
                                   break;
2150
        case TC_OCTAHEDRON       : mTouchControl = new TouchControlOctahedron(this);
2151
                                   break;
2152
        case TC_DODECAHEDRON     : mTouchControl = new TouchControlDodecahedron(this);
2153
                                   break;
2154
        case TC_ICOSAHEDRON      : mTouchControl = new TouchControlIcosahedron(this);
2155
                                   break;
2156
        case TC_CUBOID           : int[] numLayers = getNumLayers();
2157
                                   mTouchControl = new TouchControlCuboids(this,getDist3D(numLayers));
2158
                                   break;
2159
        case TC_BALL             : mTouchControl = new TouchControlBall(this);
2160
                                   break;
2161
        case TC_CHANGING_MIRROR  : mTouchControl = new TouchControlMirror(this);
2162
                                   break;
2163
        case TC_CHANGING_SQUARE  : mTouchControl = new TouchControlSquare(this);
2164
                                   break;
2165
        case TC_CHANGING_SHAPEMOD: mTouchControl = new TouchControlShapemod(this);
2166
                                   break;
2167
        }
2168
      }
2169
    return mTouchControl;
2170
    }
2171

    
2172
///////////////////////////////////////////////////////////////////////////////////////////////////
2173

    
2174
  protected void setReader(JsonReader reader)
2175
    {
2176
    // empty
2177
    }
2178

    
2179
///////////////////////////////////////////////////////////////////////////////////////////////////
2180
  // for JSON only
2181
  public abstract int getTouchControlType();
2182
  public abstract int getTouchControlSplit();
2183
  public abstract boolean[][] getLayerRotatable(int[] numLayers);
2184
  public abstract int[][][] getEnabled();
2185
  public abstract float[] getDist3D(int[] numLayers);
2186
  public abstract Static3D[] getFaceAxis();
2187
  public abstract ScrambleState[] getScrambleStates();
2188
  public abstract float[][] getCuts(int[] numLayers);
2189
  public abstract float getStickerRadius();
2190
  public abstract float getStickerStroke();
2191
  public abstract float[][] getStickerAngles();
2192
  public abstract int getCubitVariant(int cubit, int[] numLayers);
2193
  public abstract ObjectShape getObjectShape(int variant);
2194
  public abstract ObjectFaceShape getObjectFaceShape(int variant);
2195
  public abstract ObjectVertexEffects getVertexEffects(int variant);
2196
  public abstract int getNumCubitVariants(int[] numLayers);
2197
  public abstract float[][] getCubitPositions(int[] numLayers);
2198
  public abstract Static4D getCubitQuats(int cubit, int[] numLayers);
2199
  public abstract int getNumFaceColors();
2200
  public abstract float getScreenRatio();
2201
  public abstract int getColor(int face);
2202
  public abstract String getShortName();
2203
  public abstract ObjectSignature getSignature();
2204

    
2205
  // not only for JSON
2206
  public abstract Static3D[] getRotationAxis();
2207
  public abstract int[][] getBasicAngles();
2208
  public abstract int getNumFaces();
2209
  public abstract String getObjectName();
2210
  public abstract String getInventor();
2211
  public abstract int getYearOfInvention();
2212
  public abstract int getComplexity();
2213
  public abstract int getFOV();
2214
  public abstract String[][] getTutorials();
2215
  }
(8-8/9)