Project

General

Profile

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

distorted-objectlib / src / main / java / org / distorted / objectlib / main / TwistyObject.java @ 5618c5a9

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2020 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is proprietary software licensed under an EULA which you should have received      //
7
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
8
///////////////////////////////////////////////////////////////////////////////////////////////////
9

    
10
package org.distorted.objectlib.main;
11

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

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

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

    
43
import org.distorted.objectlib.helpers.FactoryCubit;
44
import org.distorted.objectlib.helpers.FactorySticker;
45
import org.distorted.objectlib.helpers.ObjectFaceShape;
46
import org.distorted.objectlib.helpers.ObjectLibInterface;
47
import org.distorted.objectlib.helpers.ObjectShape;
48
import org.distorted.objectlib.helpers.ObjectSignature;
49
import org.distorted.objectlib.helpers.ObjectSticker;
50
import org.distorted.objectlib.helpers.ObjectStickerOverride;
51
import org.distorted.objectlib.helpers.ObjectVertexEffects;
52
import org.distorted.objectlib.helpers.QuatGroupGenerator;
53
import org.distorted.objectlib.scrambling.ObjectScrambler;
54
import org.distorted.objectlib.json.JsonReader;
55
import org.distorted.objectlib.scrambling.ScrambleEdgeGenerator;
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
  private static final int STATE_NOTHING = 0;
82
  private static final int STATE_ROTATE  = 1;
83
  private static final int STATE_FINISH  = 2;
84

    
85
  public static final int TEXTURE_HEIGHT = 256;
86
  static final int NUM_STICKERS_IN_ROW = 4;
87

    
88
  public static final float SQ2 = (float)Math.sqrt(2);
89
  public static final float SQ3 = (float)Math.sqrt(3);
90
  public static final float SQ5 = (float)Math.sqrt(5);
91
  public static final float SQ6 = (float)Math.sqrt(6);
92

    
93
  private static final float MAX_SIZE_CHANGE = 1.35f;
94
  private static final float MIN_SIZE_CHANGE = 0.75f;
95

    
96
  private static final Static3D CENTER = new Static3D(0,0,0);
97
  private static final int POST_ROTATION_MILLISEC = 500;
98

    
99
  protected float[][] mStickerCoords;
100
  protected Static4D[] mObjectQuats;
101

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

    
159
///////////////////////////////////////////////////////////////////////////////////////////////////
160

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

    
182
///////////////////////////////////////////////////////////////////////////////////////////////////
183

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

    
194
///////////////////////////////////////////////////////////////////////////////////////////////////
195

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

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

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

    
207
///////////////////////////////////////////////////////////////////////////////////////////////////
208

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

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

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

    
238
    int scramblingType = getScrambleType();
239
    int[][] edges = getScrambleEdges();
240
    int[][] algorithms = getScrambleAlgorithms();
241

    
242
    mScrambler = new ObjectScrambler(scramblingType,mNumAxis,mNumLayers,algorithms,edges);
243

    
244
    boolean bandaged=false;
245

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

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

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

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

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

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

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

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

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

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

    
291
///////////////////////////////////////////////////////////////////////////////////////////////////
292

    
293
  private void createQuaternionEffects()
294
    {
295
    if( mNumQuats<=ObjectControl.MAX_QUATS )
296
      {
297
      mIsInMixupMode = false;
298

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

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

    
325
///////////////////////////////////////////////////////////////////////////////////////////////////
326

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

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

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

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

    
348
///////////////////////////////////////////////////////////////////////////////////////////////////
349

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

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

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

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

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

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

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

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

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

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

    
405
///////////////////////////////////////////////////////////////////////////////////////////////////
406

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

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

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

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

    
436
      mMesh = new MeshJoined(cubitMesh);
437

    
438
      float pillowCoeff = getPillowCoeff();
439

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

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

    
457
///////////////////////////////////////////////////////////////////////////////////////////////////
458

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

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

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

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

    
478
    return mesh;
479
    }
480

    
481
///////////////////////////////////////////////////////////////////////////////////////////////////
482

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

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

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

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

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

    
513
    setTexture();
514
    }
515

    
516
///////////////////////////////////////////////////////////////////////////////////////////////////
517

    
518
  public InitData getInitData()
519
    {
520
    return mInitData;
521
    }
522

    
523
///////////////////////////////////////////////////////////////////////////////////////////////////
524

    
525
  public boolean isInIconMode()
526
    {
527
    return mIconMode==MODE_ICON;
528
    }
529

    
530
///////////////////////////////////////////////////////////////////////////////////////////////////
531

    
532
  public int getVariantStickerShape(int variant, int face)
533
    {
534
    return face>=mStickerVariants[variant].length ? -1 : mStickerVariants[variant][face];
535
    }
536

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

    
539
  public boolean shouldResetTextureMaps()
540
    {
541
    return false;
542
    }
543

    
544
///////////////////////////////////////////////////////////////////////////////////////////////////
545

    
546
  public int[][] getScrambleAlgorithms()
547
    {
548
    return ScrambleEdgeGenerator.getScramblingAlgorithms(mBasicAngles);
549
    }
550

    
551
///////////////////////////////////////////////////////////////////////////////////////////////////
552

    
553
  private boolean sticksOut(Static3D[] faceAxis, float[] dist, float x, float y, float z )
554
    {
555
    final float MAXERR = 0.05f;
556
    int numAxis = dist.length;
557

    
558
    for(int i=0; i<numAxis; i++)
559
      {
560
      Static3D ax = faceAxis[i];
561
      float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
562
      if( len>mSize*dist[i]+MAXERR ) return true;
563
      }
564

    
565
    return false;
566
    }
567

    
568
///////////////////////////////////////////////////////////////////////////////////////////////////
569

    
570
  private boolean doesNotStickOut(int variant, float px, float py, float pz, float[] tmp, Static4D quat)
571
    {
572
    float[][] vertices = mShapes[variant].getVertices();
573
    Static3D[] axis = getFaceAxis();
574
    float[] dist3D = getDist3D(mNumLayers);
575

    
576
    for( float[] vertex : vertices)
577
      {
578
      float x = vertex[0];
579
      float y = vertex[1];
580
      float z = vertex[2];
581

    
582
      QuatHelper.rotateVectorByQuat(tmp, x, y, z, 1, quat);
583

    
584
      float mx = tmp[0] + px;
585
      float my = tmp[1] + py;
586
      float mz = tmp[2] + pz;
587

    
588
      if( sticksOut(axis, dist3D, mx,my,mz) ) return false;
589
      }
590

    
591
    return true;
592
    }
593

    
594
///////////////////////////////////////////////////////////////////////////////////////////////////
595

    
596
  private float computeAvg(float[] pos, int offset)
597
    {
598
    int len = pos.length/3;
599
    float ret=0.0f;
600
    for(int i=0; i<len; i++) ret += pos[3*i+offset];
601
    ret /= len;
602

    
603
    return ret;
604
    }
605

    
606
///////////////////////////////////////////////////////////////////////////////////////////////////
607

    
608
  protected void displayCubitQuats()
609
    {
610
    StringBuilder builder = new StringBuilder();
611
    float[] tmp = new float[4];
612
    float ERR = 0.01f;
613

    
614
    for(int cubit=0; cubit<mNumCubits; cubit++)
615
      {
616
      builder.append(cubit);
617
      builder.append(" : ");
618

    
619
      int refCubit,variant = getCubitVariant(cubit,mNumLayers);
620

    
621
      for(refCubit=0; refCubit<mNumCubits; refCubit++)
622
        {
623
        if( getCubitVariant(refCubit,mNumLayers)==variant ) break;
624
        }
625

    
626
      float[] curpos = mOrigPos[cubit];
627
      float[] refpos = mOrigPos[refCubit];
628
      float refX = computeAvg(refpos,0);
629
      float refY = computeAvg(refpos,1);
630
      float refZ = computeAvg(refpos,2);
631
      float curX = computeAvg(curpos,0);
632
      float curY = computeAvg(curpos,1);
633
      float curZ = computeAvg(curpos,2);
634

    
635
      for(int quat=0; quat<mNumQuats; quat++)
636
        {
637
        QuatHelper.rotateVectorByQuat(tmp,refX,refY,refZ,0,mObjectQuats[quat]);
638

    
639
        float dx = tmp[0]-curX;
640
        float dy = tmp[1]-curY;
641
        float dz = tmp[2]-curZ;
642

    
643
        if( dx>-ERR && dx<ERR && dy>-ERR && dy<ERR && dz>-ERR && dz<ERR )
644
          {
645
          if( doesNotStickOut(variant,curX,curY,curZ,tmp,mObjectQuats[quat]) )
646
            {
647
            builder.append(quat);
648
            builder.append(',');
649
            }
650
          else
651
            {
652
            android.util.Log.e("D", "cubit: "+cubit+" quat: "+quat+" : center correct, but shape sticks out");
653
            }
654
          }
655
        }
656

    
657
      builder.append('\n');
658
      }
659

    
660
    android.util.Log.e("D", "cubitQuats: \n"+builder );
661
    }
662

    
663
///////////////////////////////////////////////////////////////////////////////////////////////////
664

    
665
  public int getCubitRotationType(int cubit)
666
    {
667
    return TwistyObjectCubit.TYPE_NORMAL;
668
    }
669

    
670
///////////////////////////////////////////////////////////////////////////////////////////////////
671

    
672
  float[] getTrackingPoint(int cubitIndex, int cubitType)
673
    {
674
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
675
      {
676
      int variant = getCubitVariant(cubitIndex,mNumLayers);
677

    
678
      // object must have been created from JSON
679
      if( mVariantFaceIsOuter==null || mVariantFaceIsOuter[variant]==null )
680
        {
681
        mVariantFaceIsOuter = getVariantFaceIsOuter();
682
        }
683
      if( mShapes==null )
684
        {
685
        mShapes = new ObjectShape[mNumCubitVariants];
686
        }
687
      if( mShapes[variant]==null )
688
        {
689
        mShapes[variant] = getObjectShape(variant);
690
        }
691
      if( mOrigQuat==null )
692
        {
693
        mOrigQuat = new Static4D[mNumCubits];
694
        }
695
      if( mOrigQuat[cubitIndex]==null )
696
        {
697
        mOrigQuat[cubitIndex] = getCubitQuats(cubitIndex,mNumLayers);
698
        }
699

    
700
      int[][] indices = mShapes[variant].getVertIndices();
701
      int outer=-1, faces = indices.length;
702

    
703
      for(int i=0; i<faces; i++)
704
        {
705
        if( mVariantFaceIsOuter[variant][i]==1 )
706
          {
707
          outer=i;
708
          break;
709
          }
710
        }
711

    
712
      if( outer>=0 )
713
        {
714
        int vertIndex = indices[outer][0];
715
        float[] vertices = mShapes[variant].getVertices()[vertIndex];
716
        float[] ret = new float[3];
717
        float[] curpos = mOrigPos[cubitIndex];
718
        Static4D quat = mOrigQuat[cubitIndex];
719
        QuatHelper.rotateVectorByQuat(mTmp, vertices[0], vertices[1], vertices[2], 1, quat);
720

    
721
        ret[0] = mTmp[0]+computeAvg(curpos,0);
722
        ret[1] = mTmp[1]+computeAvg(curpos,1);
723
        ret[2] = mTmp[2]+computeAvg(curpos,2);
724

    
725
        return ret;
726
        }
727
      else
728
        {
729
        android.util.Log.e("D", "Error in getTrackingPoint: no outer face??");
730
        }
731
      }
732

    
733
    return null;
734
    }
735

    
736
///////////////////////////////////////////////////////////////////////////////////////////////////
737

    
738
  public int computeCurrentPuzzleFace(int type, float[] vertex)
739
    {
740
    if( type!=TwistyObjectCubit.TYPE_NORMAL )
741
      {
742
      Static3D[] axis = getFaceAxis();
743
      float[] dist3D = getDist3D(mNumLayers);
744
      final float MAXERR = 0.98f;
745
      int numAxis = axis.length;
746
      float x = vertex[0];
747
      float y = vertex[1];
748
      float z = vertex[2];
749

    
750
      for(int i=0; i<numAxis; i++)
751
        {
752
        Static3D ax = axis[i];
753
        float len = ax.get0()*x + ax.get1()*y + ax.get2()*z;
754
        if( len>mSize*dist3D[i]*MAXERR ) return i;
755
        }
756

    
757
      return -2;
758
      }
759

    
760
    return -1;
761
    }
762

    
763
///////////////////////////////////////////////////////////////////////////////////////////////////
764

    
765
  public float[] getCubitRowOffset(int cubitIndex)
766
    {
767
    return null;
768
    }
769

    
770
///////////////////////////////////////////////////////////////////////////////////////////////////
771

    
772
  void setRotationRowOffset(int puzzleFace, float[] offset)
773
    {
774
    mRowOffsets[puzzleFace][0] = offset[0];
775
    mRowOffsets[puzzleFace][1] = offset[1];
776
    mRowOffsets[puzzleFace][2] = offset[2];
777
    }
778

    
779
///////////////////////////////////////////////////////////////////////////////////////////////////
780

    
781
  int getNumAxis()
782
    {
783
    return mNumAxis;
784
    }
785

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

    
788
  public int[][] getSolvedQuats()
789
    {
790
    return mSolved.getSolvedQuats(mNumLayers,mNumCubitFaces,mCubitFaceColors);
791
    }
792

    
793
///////////////////////////////////////////////////////////////////////////////////////////////////
794

    
795
  public int getSolvedFunctionIndex()
796
    {
797
    return 0;
798
    }
799

    
800
///////////////////////////////////////////////////////////////////////////////////////////////////
801

    
802
  int computeRow(float[] pos, int axisIndex, int cubitType, int puzzleFace)
803
    {
804
    int ret=0;
805
    int len = pos.length / 3;
806
    Static3D axis = mAxis[axisIndex];
807
    float axisX = axis.get0();
808
    float axisY = axis.get1();
809
    float axisZ = axis.get2();
810
    float casted, xoff=0, yoff=0, zoff=0;
811

    
812
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
813
      {
814
      xoff = mRowOffsets[puzzleFace][0];
815
      yoff = mRowOffsets[puzzleFace][1];
816
      zoff = mRowOffsets[puzzleFace][2];
817
      }
818

    
819
    for(int i=0; i<len; i++)
820
      {
821
      casted = (pos[3*i]+xoff)*axisX + (pos[3*i+1]+yoff)*axisY + (pos[3*i+2]+zoff)*axisZ;
822
      ret |= computeSingleRow(axisIndex,casted);
823
      }
824

    
825
    return ret;
826
    }
827

    
828
///////////////////////////////////////////////////////////////////////////////////////////////////
829

    
830
  private int computeSingleRow(int axisIndex,float casted)
831
    {
832
    int num = mNumCuts[axisIndex];
833

    
834
    for(int i=0; i<num; i++)
835
      {
836
      if( casted<mCuts[axisIndex][i] ) return (1<<i);
837
      }
838

    
839
    return (1<<num);
840
    }
841

    
842
///////////////////////////////////////////////////////////////////////////////////////////////////
843

    
844
  private boolean wasRotateApplied()
845
    {
846
    return mEffects.exists(mRotateEffect.getID());
847
    }
848

    
849
///////////////////////////////////////////////////////////////////////////////////////////////////
850

    
851
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
852
    {
853
    return (mCubits[cubit].getRotRow(axis) & rowBitmap) != 0;
854
    }
855

    
856
///////////////////////////////////////////////////////////////////////////////////////////////////
857
// note the minus in front of the sin() - we rotate counterclockwise
858
// when looking towards the direction where the axis increases in values.
859

    
860
  private Static4D makeQuaternion(float axisX, float axisY, float axisZ, int angleInDegrees)
861
    {
862
    while( angleInDegrees<0 ) angleInDegrees += 360;
863
    angleInDegrees %= 360;
864
    
865
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
866
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
867

    
868
    return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
869
    }
870

    
871
///////////////////////////////////////////////////////////////////////////////////////////////////
872

    
873
  private synchronized void setupPosition(int[][] moves)
874
    {
875
    if( moves!=null )
876
      {
877
      Static4D quat;
878
      int index, axisIndex, row, rowBitmap, basic, angle;
879

    
880
      for(int[] move: moves)
881
        {
882
        axisIndex= move[0];
883
        rowBitmap= computeBitmapFromRow( move[1],axisIndex);
884
        row      = computeRowFromBitmap( move[1] );
885
        basic    = mBasicAngles[axisIndex][row];
886
        angle    = move[2]*(360/basic);   // this assumes that all layers from
887
                                          // the bitmap have the same BasicAngle.
888
                                          // at the moment this is always true as
889
                                          // there are no bandaged objects with
890
                                          // different per-layer BasicAngles.
891
        Static3D axis = mAxis[axisIndex];
892
        float axisX = axis.get0();
893
        float axisY = axis.get1();
894
        float axisZ = axis.get2();
895
        quat = makeQuaternion(axisX,axisY,axisZ,angle);
896

    
897
        for(int i=0; i<mNumCubits; i++)
898
          {
899
          mBelongs[i] = belongsToRotation(i,axisIndex,rowBitmap);
900
          if( mBelongs[i] )
901
            {
902
            boolean result = mCubits[i].rotateCubit(quat);
903
            if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,1);
904
            }
905
          }
906

    
907
        recomputeFaceOffsets();
908

    
909
        for(int i=0; i<mNumCubits; i++)
910
          {
911
          if( mBelongs[i] )
912
            {
913
            index = mCubits[i].postRotateCubit(quat);
914
            setCubitQuat(i,mCubits[i].computeAssociation(),index);
915
            }
916
          else if( mCubits[i].getType()==TwistyObjectCubit.TYPE_FOLLOWER )
917
            {
918
            mCubits[i].computeRotationRow();
919
            setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
920
            }
921
          }
922
        }
923
      }
924
    }
925

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

    
928
  public int getScrambleType()
929
    {
930
    return 0;
931
    }
932

    
933
///////////////////////////////////////////////////////////////////////////////////////////////////
934

    
935
  int computeBitmapFromRow(int rowBitmap, int axis)
936
    {
937
    if( mIsBandaged )
938
      {
939
      int bitmap, initBitmap=0;
940

    
941
      while( initBitmap!=rowBitmap )
942
        {
943
        initBitmap = rowBitmap;
944

    
945
        for(int cubit=0; cubit<mNumCubits; cubit++)
946
          {
947
          bitmap = mCubits[cubit].getRotRow(axis);
948
          if( (rowBitmap & bitmap) != 0 ) rowBitmap |= bitmap;
949
          }
950
        }
951
      }
952

    
953
    return rowBitmap;
954
    }
955

    
956
///////////////////////////////////////////////////////////////////////////////////////////////////
957

    
958
  private int computeRowFromBitmap(int rowBitmap)
959
    {
960
    int index = 0;
961

    
962
    while(index<32)
963
      {
964
      if( (rowBitmap&0x1) != 0 ) return index;
965
      rowBitmap>>=1;
966
      index++;
967
      }
968
    return 0;
969
    }
970

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

    
975
  void clampPos(float[] pos, int offset)
976
    {
977
    float currError, minError = Float.MAX_VALUE;
978
    int minErrorIndex1 = -1;
979
    int minErrorIndex2 = -1;
980

    
981
    float x = pos[offset  ];
982
    float y = pos[offset+1];
983
    float z = pos[offset+2];
984

    
985
    float xo,yo,zo;
986

    
987
    for(int i=0; i<mNumCubits; i++)
988
      {
989
      int len = mOrigPos[i].length / 3;
990

    
991
      for(int j=0; j<len; j++)
992
        {
993
        xo = mOrigPos[i][3*j  ];
994
        yo = mOrigPos[i][3*j+1];
995
        zo = mOrigPos[i][3*j+2];
996

    
997
        currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
998

    
999
        if( currError<minError )
1000
          {
1001
          minError = currError;
1002
          minErrorIndex1 = i;
1003
          minErrorIndex2 = j;
1004
          }
1005
        }
1006
      }
1007

    
1008
    if( minError< 0.05f ) // TODO: 0.05 ?
1009
      {
1010
      pos[offset  ] = mOrigPos[minErrorIndex1][3*minErrorIndex2  ];
1011
      pos[offset+1] = mOrigPos[minErrorIndex1][3*minErrorIndex2+1];
1012
      pos[offset+2] = mOrigPos[minErrorIndex1][3*minErrorIndex2+2];
1013
      }
1014
    }
1015

    
1016
///////////////////////////////////////////////////////////////////////////////////////////////////
1017

    
1018
  private float getAngle()
1019
    {
1020
    mPointNum = mRotationAngle.getNumPoints();
1021
    return mPointNum>=1 ? mRotationAngle.getPoint(mPointNum-1).get0() : 0;
1022
    }
1023

    
1024
///////////////////////////////////////////////////////////////////////////////////////////////////
1025

    
1026
  void setLibInterface(ObjectLibInterface inter)
1027
    {
1028
    mInterface = inter;
1029
    }
1030

    
1031
///////////////////////////////////////////////////////////////////////////////////////////////////
1032

    
1033
  void applyScrambles(int[][] moves)
1034
    {
1035
    setupPosition(moves);
1036
    }
1037

    
1038
///////////////////////////////////////////////////////////////////////////////////////////////////
1039

    
1040
  void initializeObject(int[][] moves)
1041
    {
1042
    solve();
1043
    setupPosition(moves);
1044
    }
1045

    
1046
///////////////////////////////////////////////////////////////////////////////////////////////////
1047

    
1048
  synchronized void removeRotationNow()
1049
    {
1050
    float angle = getAngle();
1051
    double nearestAngleInRadians = angle*Math.PI/180;
1052
    float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
1053
    float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
1054
    float axisX = mAxis[mCurrentRotAxis].get0();
1055
    float axisY = mAxis[mCurrentRotAxis].get1();
1056
    float axisZ = mAxis[mCurrentRotAxis].get2();
1057
    Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
1058

    
1059
    mRotationAngleStatic.set0(0);
1060
    mRotationAngle.removeAll();
1061

    
1062
    for(int i=0; i<mNumCubits; i++)
1063
      {
1064
      mBelongs[i] = belongsToRotation(i, mCurrentRotAxis,mRotRowBitmap);
1065
      if( mBelongs[i] )
1066
        {
1067
        boolean result = mCubits[i].rotateCubit(quat);
1068
        if( !result ) debugQuat(quat,i,axisX,axisY,axisZ,angle,2);
1069
        }
1070
      }
1071

    
1072
    recomputeFaceOffsets();
1073

    
1074
    for(int i=0; i<mNumCubits; i++)
1075
      {
1076
      if( mBelongs[i] )
1077
        {
1078
        int index = mCubits[i].postRotateCubit(quat);
1079
        setCubitQuat(i,mCubits[i].computeAssociation(),index);
1080
        }
1081
      else if( mCubits[i].getType()==TwistyObjectCubit.TYPE_FOLLOWER )
1082
        {
1083
        mCubits[i].computeRotationRow();
1084
        setCubitQuat(i,mCubits[i].computeAssociation(),mCubits[i].mQuatIndex);
1085
        }
1086
      }
1087

    
1088
    mRotationState = STATE_NOTHING;
1089
    }
1090

    
1091
///////////////////////////////////////////////////////////////////////////////////////////////////
1092

    
1093
  private void recomputeFaceOffsets()
1094
    {
1095
    for(int i=0; i<mNumPuzzleFaces; i++)
1096
      {
1097
      mRowOffsets[i][0] =0;
1098
      mRowOffsets[i][1] =0;
1099
      mRowOffsets[i][2] =0;
1100
      }
1101

    
1102
    for(int i=0; i<mNumCubits; i++)
1103
      if( mCubits[i].getType()==TwistyObjectCubit.TYPE_DECIDER )
1104
        {
1105
        float[] offset = mCubits[i].getOffset();
1106
        int face = mCubits[i].getPuzzleFace();
1107
        mRowOffsets[face][0] = offset[0];
1108
        mRowOffsets[face][1] = offset[1];
1109
        mRowOffsets[face][2] = offset[2];
1110
        }
1111
    }
1112

    
1113
///////////////////////////////////////////////////////////////////////////////////////////////////
1114

    
1115
  long finishRotationNow(EffectListener listener, int nearestAngleInDegrees)
1116
    {
1117
    if( wasRotateApplied() )
1118
      {
1119
      mRotationState = STATE_FINISH;
1120
      float angle = getAngle();
1121
      mRotationAngleStatic.set0(angle);
1122
      mRotationAngleFinal.set0(nearestAngleInDegrees);
1123
      mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
1124

    
1125
      mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
1126
      mRotationAngle.resetToBeginning();
1127
      mRotationAngle.removeAll();
1128
      mRotationAngle.add(mRotationAngleStatic);
1129
      mRotationAngle.add(mRotationAngleMiddle);
1130
      mRotationAngle.add(mRotationAngleFinal);
1131
      mRotateEffect.notifyWhenFinished(listener);
1132

    
1133
      return mRotateEffect.getID();
1134
      }
1135

    
1136
    return 0;
1137
    }
1138

    
1139
///////////////////////////////////////////////////////////////////////////////////////////////////
1140

    
1141
  synchronized long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
1142
    {
1143
    int mult = 1;
1144

    
1145
    if( wasRotateApplied() )
1146
      {
1147
      if( mRotationState==STATE_ROTATE )
1148
        {
1149
        return 0;
1150
        }
1151
      if( mRotationState==STATE_FINISH )
1152
        {
1153
        removeRotationNow();
1154
        mult = -1;
1155
        }
1156

    
1157
      mRotationState = STATE_ROTATE;
1158
      mCurrentRotAxis = axis;
1159
      mRotRowBitmap= computeBitmapFromRow( rowBitmap,axis );
1160
      mRotationAngleStatic.set0(0.0f);
1161
      mRotationAxis.set( mAxis[axis] );
1162
      mRotationAngle.setDuration(durationMillis);
1163
      mRotationAngle.resetToBeginning();
1164
      mRotationAngle.add(new Static1D(0));
1165
      mRotationAngle.add(new Static1D(angle));
1166
      mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1167
      mRotateEffect.notifyWhenFinished(listener);
1168
      return mult*mRotateEffect.getID();
1169
      }
1170

    
1171
    return 0;
1172
    }
1173

    
1174
///////////////////////////////////////////////////////////////////////////////////////////////////
1175

    
1176
  void continueRotation(float angleInDegrees)
1177
    {
1178
    mRotationAngleStatic.set0(angleInDegrees);
1179
    }
1180

    
1181
///////////////////////////////////////////////////////////////////////////////////////////////////
1182

    
1183
  synchronized boolean beginNewRotation(int axis, int row )
1184
    {
1185
    if( mRotationState==STATE_ROTATE )
1186
      {
1187
      return false;
1188
      }
1189
    if( mRotationState==STATE_FINISH )
1190
      {
1191
      removeRotationNow();
1192
      }
1193

    
1194
    if( axis<0 || axis>=mNumAxis )
1195
      {
1196
      android.util.Log.e("object", "invalid rotation axis: "+axis);
1197
      return false;
1198
      }
1199
    if( row<0 || row>=mNumLayers[axis] )
1200
      {
1201
      android.util.Log.e("object", "invalid rotation row: "+row);
1202
      return false;
1203
      }
1204

    
1205
    mRotationState = STATE_ROTATE;
1206
    mCurrentRotAxis = axis;
1207
    mRotRowBitmap= computeBitmapFromRow( (1<<row),axis );
1208
    mRotationAngleStatic.set0(0.0f);
1209
    mRotationAxis.set( mAxis[axis] );
1210
    mRotationAngle.add(mRotationAngleStatic);
1211
    mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*mMaxNumLayers) , -1);
1212

    
1213
    return true;
1214
    }
1215

    
1216
///////////////////////////////////////////////////////////////////////////////////////////////////
1217

    
1218
  void setTextureMap(int cubit, int face, int color)
1219
    {
1220
    int variant  = getCubitVariant(cubit,mNumLayers);
1221
    int shape    = getVariantStickerShape(variant,face);
1222
    int texIndex = color<0 || shape<0 ? mNumTextures-mNumOverrides : shape*mNumFaceColors + color;
1223
    int row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1224
    int col      = texIndex%mNumTexCols;
1225

    
1226
    final float ratioW = 1.0f/mNumTexCols;
1227
    final float ratioH = 1.0f/mNumTexRows;
1228
    final Static4D[] maps = new Static4D[1];
1229
    maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1230
    mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1231
    }
1232

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

    
1235
  private int getCubitFaceColor(int cubit, int face)
1236
    {
1237
    int puzzleFace = getCubitFaceMap(cubit,face);
1238
    if( puzzleFace>=0 ) puzzleFace %= mNumFaceColors;
1239
    return puzzleFace;
1240
    }
1241

    
1242
///////////////////////////////////////////////////////////////////////////////////////////////////
1243

    
1244
  public int getCubitFaceMap(int cubit, int face)
1245
    {
1246
    int numFaces = mCubitFaceColors[cubit].length;
1247
    int puzzleFace = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
1248
    return puzzleFace<0 ? -1 : puzzleFace;
1249
    }
1250

    
1251
///////////////////////////////////////////////////////////////////////////////////////////////////
1252

    
1253
  void resetAllTextureMaps()
1254
    {
1255
    final float ratioW = 1.0f/mNumTexCols;
1256
    final float ratioH = 1.0f/mNumTexRows;
1257
    int cubColor, stiShape, texIndex, variant, row, col;
1258

    
1259
    for(int cubit=0; cubit<mNumCubits; cubit++)
1260
      {
1261
      final Static4D[] maps = new Static4D[mNumCubitFaces];
1262
      variant = getCubitVariant(cubit,mNumLayers);
1263

    
1264
      for(int face=0; face<mNumCubitFaces; face++)
1265
        {
1266
        cubColor = getCubitFaceColor(cubit,face);
1267
        stiShape = getVariantStickerShape(variant,face);
1268
        texIndex = cubColor<0 || stiShape<0 ? mNumTextures-mNumOverrides : stiShape*mNumFaceColors + cubColor;
1269
        row      = (mNumTexRows-1) - texIndex/mNumTexCols;
1270
        col      = texIndex%mNumTexCols;
1271

    
1272
        maps[face] = new Static4D( col*ratioW, row*ratioH, ratioW, ratioH);
1273
        }
1274

    
1275
      mMesh.setTextureMap(maps,mNumCubitFaces*cubit);
1276
      }
1277

    
1278
    overrideCubitFaceColor();
1279
    }
1280

    
1281
///////////////////////////////////////////////////////////////////////////////////////////////////
1282

    
1283
  private void overrideCubitFaceColor()
1284
    {
1285
    final float ratioW = 1.0f/mNumTexCols;
1286
    final float ratioH = 1.0f/mNumTexRows;
1287

    
1288
    for(int i=0; i<mNumOverrides; i++)
1289
      {
1290
      int[] cubitFaces = mStickerOverrides[i].getCubitFaces();
1291
      int length = cubitFaces.length/2;
1292

    
1293
      for(int j=0; j<length; j++)
1294
        {
1295
        final Static4D[] maps = new Static4D[1];
1296
        int color = mNumTextures-mNumOverrides+1+i;
1297
        int row   = (mNumTexRows-1) - color/mNumTexCols;
1298
        int col   = color%mNumTexCols;
1299
        int cubit = cubitFaces[2*j];
1300
        int face  = cubitFaces[2*j+1];
1301
        maps[0] = new Static4D(col*ratioW, row*ratioH, ratioW, ratioH);
1302
        mMesh.setTextureMap(maps,mNumCubitFaces*cubit+face);
1303
        }
1304
      }
1305
    }
1306

    
1307
///////////////////////////////////////////////////////////////////////////////////////////////////
1308

    
1309
  void releaseResources()
1310
    {
1311
    mTexture.markForDeletion();
1312
    mMesh.markForDeletion();
1313
    mEffects.markForDeletion();
1314

    
1315
    for(int j=0; j<mNumCubits; j++)
1316
      {
1317
      mCubits[j].releaseResources();
1318
      }
1319
    }
1320

    
1321
///////////////////////////////////////////////////////////////////////////////////////////////////
1322

    
1323
  private void setCubitQuat(int cubit, int andAssociation, int equAssociation)
1324
    {
1325
    if( !mIsInMixupMode )
1326
      {
1327
      mMesh.setEffectAssociation(cubit,andAssociation,equAssociation);
1328
      }
1329
    else
1330
      {
1331
      mMesh.setEffectAssociation(cubit,andAssociation,cubit);
1332
      Static4D tmp = mObjectQuats[equAssociation];
1333
      mMixupModeQuats[cubit].set(tmp);
1334
      }
1335
    }
1336

    
1337
///////////////////////////////////////////////////////////////////////////////////////////////////
1338

    
1339
  synchronized void restorePreferences(SharedPreferences preferences)
1340
    {
1341
    boolean error = false;
1342
    String key = getShortName();
1343

    
1344
    for(int i=0; i<mNumCubits; i++)
1345
      {
1346
      mQuatDebug[i] = mCubits[i].restorePreferences(key,preferences);
1347

    
1348
      if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1349
        {
1350
        boolean result = mCubits[i].rotateCubit(mObjectQuats[mQuatDebug[i]]);
1351
        if( !result ) debugQuat(mObjectQuats[mQuatDebug[i]],i,0,0,0,mQuatDebug[i],3);
1352
        }
1353
      else { error = true; break; }
1354
      }
1355

    
1356
    if( !error )
1357
      {
1358
      recomputeFaceOffsets();
1359

    
1360
      for(int i=0; i<mNumCubits; i++)
1361
        {
1362
        if( mQuatDebug[i]>=0 && mQuatDebug[i]<mNumQuats )
1363
          {
1364
          mCubits[i].computeRotationRow();
1365
          setCubitQuat(i,mCubits[i].computeAssociation(),mQuatDebug[i]);
1366
          }
1367
        else { error = true; break; }
1368
        }
1369
      }
1370

    
1371
    if( error )
1372
      {
1373
      for(int i=0; i<mNumCubits; i++)
1374
        {
1375
        mCubits[i].solve();
1376
        setCubitQuat(i,mCubits[i].computeAssociation(),0);
1377
        }
1378
      }
1379
    }
1380

    
1381
///////////////////////////////////////////////////////////////////////////////////////////////////
1382

    
1383
  void savePreferences(SharedPreferences.Editor editor)
1384
    {
1385
    String key = getShortName();
1386
    for(int i=0; i<mNumCubits; i++) mCubits[i].savePreferences(key,editor);
1387
    }
1388

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

    
1391
  public void removePreferences(SharedPreferences.Editor editor)
1392
    {
1393
    String key = getShortName();
1394
    for(int i=0; i<mNumCubits; i++) mCubits[i].removePreferences(key,editor);
1395
    }
1396

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

    
1399
  private float computeRadiusCorrection(float[] sticker, int curr, int len)
1400
    {
1401
    final float A = 0.8f;  // 0<A<1
1402

    
1403
    int prev = curr>0 ? curr-1 : len-1;
1404
    int next = curr<len-1 ? curr+1 : 0;
1405

    
1406
    float v1x = sticker[2*prev  ]-sticker[2*curr  ];
1407
    float v1y = sticker[2*prev+1]-sticker[2*curr+1];
1408
    float v2x = sticker[2*next  ]-sticker[2*curr  ];
1409
    float v2y = sticker[2*next+1]-sticker[2*curr+1];
1410

    
1411
    float len1= v1x*v1x+v1y*v1y;
1412
    float len2= v2x*v2x+v2y*v2y;
1413

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

    
1416
    return 1-A*cos;
1417
    }
1418

    
1419
///////////////////////////////////////////////////////////////////////////////////////////////////
1420
// Radius of the sphere circumscribed on the puzzle. Needed for pillowing.
1421
//
1422
// This won't work correctly for pillowing off-center puzzles (e.g. mirrors) - for those we'd need
1423
// to introduce the concept of a 'sink center' as well.
1424
//
1425
// public because needed in TouchControlShapemod
1426

    
1427
  public float getCircumscribedRadius()
1428
    {
1429
    switch(mNumPuzzleFaces)
1430
      {
1431
      case  4: return (SQ6/4)*mSize;
1432
      case  6: return (SQ3/2)*mSize;
1433
      case  8: return (SQ2/2)*mSize;
1434
      case 12: return (SQ3/2)*((SQ5+1)/2)*mSize;
1435
      case 16: return 0.50f*mSize;
1436
      }
1437

    
1438
    return 0.0f;
1439
    }
1440

    
1441
///////////////////////////////////////////////////////////////////////////////////////////////////
1442

    
1443
  public ObjectSticker retSticker(int sticker)
1444
    {
1445
    if( mStickers==null )
1446
      {
1447
      float rad = getStickerRadius();
1448
      float str = getStickerStroke();
1449
      float[][] angles = getStickerAngles();
1450
      int numStickers = mStickerCoords.length;
1451
      mStickers = new ObjectSticker[numStickers];
1452

    
1453
      for(int s=0; s<numStickers; s++)
1454
        {
1455
        float scale = mStickerScales.length>s ? mStickerScales[s] : 1.0f;
1456
        float radius = rad / scale;
1457
        float stroke = str / scale;
1458
        int len = mStickerCoords[s].length/2;
1459
        float[] radii = new float[len];
1460
        for(int r=0; r<len; r++) radii[r] = radius*computeRadiusCorrection(mStickerCoords[s],r,len);
1461
        mStickers[s] = new ObjectSticker(mStickerCoords[s],angles==null ? null : angles[s],radii,stroke);
1462
        }
1463
      }
1464

    
1465
    return mStickers[sticker];
1466
    }
1467

    
1468
///////////////////////////////////////////////////////////////////////////////////////////////////
1469
// some objects (currently Kilominx,Ivy,Rex) might want to change the stickers.
1470

    
1471
  public void adjustStickerCoords()
1472
    {
1473

    
1474
    }
1475

    
1476
///////////////////////////////////////////////////////////////////////////////////////////////////
1477

    
1478
  public Static4D[] getQuats()
1479
    {
1480
    if( mObjectQuats==null )
1481
      {
1482
      mObjectQuats = QuatGroupGenerator.computeGroup(mAxis,mBasicAngles);
1483
      }
1484

    
1485
    return mObjectQuats;
1486
    }
1487

    
1488
///////////////////////////////////////////////////////////////////////////////////////////////////
1489

    
1490
  public int[][] getVariantFaceIsOuter()
1491
    {
1492
    return mVariantFaceIsOuter;
1493
    }
1494

    
1495
///////////////////////////////////////////////////////////////////////////////////////////////////
1496

    
1497
  public int getInternalColor()
1498
    {
1499
    return COLOR_INTERNAL;
1500
    }
1501

    
1502
///////////////////////////////////////////////////////////////////////////////////////////////////
1503
// the getFaceColors + final INTERNAL_COLOR in a grid (so that we do not exceed the maximum texture size)
1504

    
1505
  private void createTexture()
1506
    {
1507
    Paint paint = new Paint();
1508
    mBitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_4444);
1509
    Canvas canvas = new Canvas(mBitmap);
1510

    
1511
    paint.setAntiAlias(true);
1512
    paint.setTextAlign(Paint.Align.CENTER);
1513
    paint.setStyle(Paint.Style.FILL);
1514
    paint.setColor(getInternalColor());
1515
    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
1516

    
1517
    int texture = 0;
1518
    FactorySticker factory = FactorySticker.getInstance();
1519

    
1520
    for(int row=0; row<mNumTexRows; row++)
1521
      for(int col=0; col<mNumTexCols; col++)
1522
        {
1523
        if( texture<mNumTextures-mNumOverrides )
1524
          {
1525
          ObjectSticker sticker = retSticker(texture/mNumFaceColors);
1526
          int color = getColor(texture%mNumFaceColors);
1527
          factory.drawRoundedPolygon(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color, sticker);
1528
          }
1529
        else if( texture>mNumTextures-mNumOverrides && texture<=mNumTextures )
1530
          {
1531
          int color = mStickerOverrides[mNumTextures-texture].getColor();
1532
          factory.drawSolidColor(canvas, paint, col*TEXTURE_HEIGHT, (mNumTexRows-row)*TEXTURE_HEIGHT, color);
1533
          }
1534

    
1535
        texture++;
1536
        }
1537
    }
1538

    
1539
///////////////////////////////////////////////////////////////////////////////////////////////////
1540

    
1541
  void setTexture()
1542
    {
1543
    if( mBitmap==null ) createTexture();
1544

    
1545
    if( !mTexture.setTextureAlreadyInverted(mBitmap) )
1546
      {
1547
      int max = DistortedLibrary.getMaxTextureSize();
1548
      mInterface.reportProblem("failed to set texture of size "+mBitmap.getWidth()+"x"+mBitmap.getHeight()+" max is "+max, true);
1549
      }
1550
    }
1551

    
1552
///////////////////////////////////////////////////////////////////////////////////////////////////
1553

    
1554
  void setObjectRatioNow(float sc, int nodeSize)
1555
    {
1556
    mObjectScreenRatio = sc;
1557
    float scale = mObjectScreenRatio*mInitScreenRatio*nodeSize/mSize;
1558
    mObjectScale.set(scale,scale,scale);
1559

    
1560
    if( mTouchControl ==null ) mTouchControl = getTouchControl();
1561
    mTouchControl.setObjectRatio(mObjectScreenRatio*mInitScreenRatio);
1562
    }
1563

    
1564
///////////////////////////////////////////////////////////////////////////////////////////////////
1565

    
1566
  void setObjectRatio(float sizeChange, int nodeSize)
1567
    {
1568
    mObjectScreenRatio *= (1.0f+sizeChange)/2;
1569

    
1570
    if( mObjectScreenRatio>MAX_SIZE_CHANGE) mObjectScreenRatio = MAX_SIZE_CHANGE;
1571
    if( mObjectScreenRatio<MIN_SIZE_CHANGE) mObjectScreenRatio = MIN_SIZE_CHANGE;
1572

    
1573
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1574
    }
1575

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

    
1578
  void setNodeSize(int nodeSize)
1579
    {
1580
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1581
    }
1582

    
1583
///////////////////////////////////////////////////////////////////////////////////////////////////
1584

    
1585
  public float getRatio()
1586
    {
1587
    return mObjectScreenRatio;
1588
    }
1589

    
1590
///////////////////////////////////////////////////////////////////////////////////////////////////
1591

    
1592
  public float getObjectRatio()
1593
    {
1594
    return mObjectScreenRatio*mInitScreenRatio;
1595
    }
1596

    
1597
///////////////////////////////////////////////////////////////////////////////////////////////////
1598

    
1599
  boolean isSolved()
1600
    {
1601
    return mSolved.isSolved(mCubits);
1602
    }
1603

    
1604
///////////////////////////////////////////////////////////////////////////////////////////////////
1605

    
1606
  int computeNearestAngle(int basicAngle, float angle, float speed)
1607
    {
1608
    int nearestAngle = 360/basicAngle;
1609
    int tmp = (int)((angle+nearestAngle/2)/nearestAngle);
1610
    if( angle< -(nearestAngle*0.5) ) tmp-=1;
1611

    
1612
    if( tmp!=0 ) return nearestAngle*tmp;
1613

    
1614
    return speed> 1.2f ? nearestAngle*(angle>0 ? 1:-1) : 0;
1615
    }
1616

    
1617
///////////////////////////////////////////////////////////////////////////////////////////////////
1618
// INTERNAL API - those are called from 'effects' package
1619
///////////////////////////////////////////////////////////////////////////////////////////////////
1620

    
1621
  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
1622
    {
1623
    mScrambler.randomizeNewScramble(scramble,rnd,curr,total, getSignature() );
1624
    }
1625

    
1626
///////////////////////////////////////////////////////////////////////////////////////////////////
1627

    
1628
  public Static4D getRotationQuat()
1629
    {
1630
    return mQuat;
1631
    }
1632

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

    
1635
  public float getSize()
1636
    {
1637
    return mSize;
1638
    }
1639

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

    
1642
  public void applyEffect(Effect effect, int position)
1643
    {
1644
    mEffects.apply(effect, position);
1645
    }
1646

    
1647
///////////////////////////////////////////////////////////////////////////////////////////////////
1648

    
1649
  public void removeEffect(long effectID)
1650
    {
1651
    mEffects.abortById(effectID);
1652
    }
1653

    
1654
///////////////////////////////////////////////////////////////////////////////////////////////////
1655

    
1656
  public MeshBase getObjectMesh()
1657
    {
1658
    return mMesh;
1659
    }
1660

    
1661
///////////////////////////////////////////////////////////////////////////////////////////////////
1662

    
1663
  public DistortedEffects getObjectEffects()
1664
    {
1665
    return mEffects;
1666
    }
1667

    
1668
///////////////////////////////////////////////////////////////////////////////////////////////////
1669

    
1670
  public int getCubitType(int cubit)
1671
    {
1672
    return mCubits[cubit].getType();
1673
    }
1674

    
1675
///////////////////////////////////////////////////////////////////////////////////////////////////
1676

    
1677
  public float[] getCubitOffset(int cubit)
1678
    {
1679
    return mCubits[cubit].getOffset();
1680
    }
1681

    
1682
///////////////////////////////////////////////////////////////////////////////////////////////////
1683

    
1684
  public ObjectStickerOverride[] getStickerOverrides()
1685
    {
1686
    return null;
1687
    }
1688

    
1689
///////////////////////////////////////////////////////////////////////////////////////////////////
1690

    
1691
  public boolean getError()
1692
    {
1693
    return mError;
1694
    }
1695

    
1696
///////////////////////////////////////////////////////////////////////////////////////////////////
1697

    
1698
  public String getErrorString()
1699
    {
1700
    return mErrorString;
1701
    }
1702

    
1703
///////////////////////////////////////////////////////////////////////////////////////////////////
1704
// PUBLIC API
1705
///////////////////////////////////////////////////////////////////////////////////////////////////
1706

    
1707
  public int getCubitFaceColorIndex(int cubit, int face)
1708
    {
1709
    Static4D texMap = mMesh.getTextureMap(mNumFaceColors *cubit + face);
1710

    
1711
    int x = (int)(texMap.get0()/texMap.get2());
1712
    int y = (int)(texMap.get1()/texMap.get3());
1713

    
1714
    return (mNumTexRows-1-y)*NUM_STICKERS_IN_ROW + x;
1715
    }
1716

    
1717
///////////////////////////////////////////////////////////////////////////////////////////////////
1718

    
1719
  public int[] getNumLayers()
1720
    {
1721
    return mNumLayers;
1722
    }
1723

    
1724
///////////////////////////////////////////////////////////////////////////////////////////////////
1725

    
1726
  public synchronized void solve()
1727
    {
1728
    for(int i=0; i<mNumCubits; i++)
1729
      {
1730
      mCubits[i].solve();
1731
      }
1732

    
1733
    recomputeFaceOffsets();
1734

    
1735
    for(int i=0; i<mNumCubits; i++)
1736
      {
1737
      mCubits[i].computeRotationRow();
1738
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
1739
      }
1740
    }
1741

    
1742
///////////////////////////////////////////////////////////////////////////////////////////////////
1743

    
1744
  public int getCubitQuatIndex(int cubit)
1745
    {
1746
    return (cubit>=0 && cubit<mNumCubits) ? mCubits[cubit].mQuatIndex : 0;
1747
    }
1748

    
1749
///////////////////////////////////////////////////////////////////////////////////////////////////
1750

    
1751
  public int getCubitRotRow(int cubit, int axis)
1752
    {
1753
    return mCubits[cubit].getRotRow(axis);
1754
    }
1755

    
1756
///////////////////////////////////////////////////////////////////////////////////////////////////
1757

    
1758
  public Bitmap getStickerBitmap()
1759
    {
1760
    return mBitmap;
1761
    }
1762

    
1763
///////////////////////////////////////////////////////////////////////////////////////////////////
1764

    
1765
  public DistortedNode getNode()
1766
    {
1767
    return mNode;
1768
    }
1769

    
1770
///////////////////////////////////////////////////////////////////////////////////////////////////
1771

    
1772
  public int getNumStickerTypes()
1773
    {
1774
    return mNumStickerTypes;
1775
    }
1776

    
1777
///////////////////////////////////////////////////////////////////////////////////////////////////
1778

    
1779
  public String reportState()
1780
    {
1781
    StringBuilder builder = new StringBuilder();
1782

    
1783
    for(int i=0; i<mNumCubits; i++ )
1784
      {
1785
      if( i>0 ) builder.append('.');
1786
      builder.append(mCubits[i].mQuatIndex);
1787
      }
1788

    
1789
    return builder.toString();
1790
    }
1791

    
1792
///////////////////////////////////////////////////////////////////////////////////////////////////
1793
// this is here only so it can be overridden in TwistyJSON so that we can get this from JSON.
1794

    
1795
  public int getNumCubitFaces()
1796
    {
1797
    return 0;
1798
    }
1799

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

    
1805
  public float getPillowCoeff()
1806
    {
1807
    return 1.0f;
1808
    }
1809

    
1810
///////////////////////////////////////////////////////////////////////////////////////////////////
1811

    
1812
  public TouchControl getTouchControl()
1813
    {
1814
    if( mTouchControl==null )
1815
      {
1816
      switch(getTouchControlType())
1817
        {
1818
        case TC_TETRAHEDRON      : mTouchControl = new TouchControlTetrahedron(this);
1819
                                   break;
1820
        case TC_HEXAHEDRON       : mTouchControl = new TouchControlHexahedron(this);
1821
                                   break;
1822
        case TC_OCTAHEDRON       : mTouchControl = new TouchControlOctahedron(this);
1823
                                   break;
1824
        case TC_DODECAHEDRON     : mTouchControl = new TouchControlDodecahedron(this);
1825
                                   break;
1826
        case TC_ICOSAHEDRON      : mTouchControl = new TouchControlIcosahedron(this);
1827
                                   break;
1828
        case TC_CUBOID           : int[] numLayers = getNumLayers();
1829
                                   mTouchControl = new TouchControlCuboids(this,getDist3D(numLayers));
1830
                                   break;
1831
        case TC_BALL             : mTouchControl = new TouchControlBall(this);
1832
                                   break;
1833
        case TC_CHANGING_MIRROR  : mTouchControl = new TouchControlMirror(this);
1834
                                   break;
1835
        case TC_CHANGING_SQUARE  : mTouchControl = new TouchControlSquare(this);
1836
                                   break;
1837
        case TC_CHANGING_SHAPEMOD: mTouchControl = new TouchControlShapemod(this);
1838
                                   break;
1839
        }
1840
      }
1841
    return mTouchControl;
1842
    }
1843

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

    
1846
  protected void setReader(JsonReader reader)
1847
    {
1848
    // empty
1849
    }
1850

    
1851
///////////////////////////////////////////////////////////////////////////////////////////////////
1852
  // for JSON only
1853
  public abstract int getTouchControlType();
1854
  public abstract int getTouchControlSplit();
1855
  public abstract boolean[][] getLayerRotatable(int[] numLayers);
1856
  public abstract int[][][] getEnabled();
1857
  public abstract float[] getDist3D(int[] numLayers);
1858
  public abstract Static3D[] getFaceAxis();
1859
  public abstract int[][] getScrambleEdges();
1860
  public abstract float[][] getCuts(int[] numLayers);
1861
  public abstract float getStickerRadius();
1862
  public abstract float getStickerStroke();
1863
  public abstract float[][] getStickerAngles();
1864
  public abstract int getCubitVariant(int cubit, int[] numLayers);
1865
  public abstract ObjectShape getObjectShape(int variant);
1866
  public abstract ObjectFaceShape getObjectFaceShape(int variant);
1867
  public abstract ObjectVertexEffects getVertexEffects(int variant);
1868
  public abstract int getNumCubitVariants(int[] numLayers);
1869
  public abstract float[][] getCubitPositions(int[] numLayers);
1870
  public abstract Static4D getCubitQuats(int cubit, int[] numLayers);
1871
  public abstract int getNumFaceColors();
1872
  public abstract float getScreenRatio();
1873
  public abstract int getColor(int face);
1874
  public abstract String getShortName();
1875
  public abstract ObjectSignature getSignature();
1876

    
1877
  // not only for JSON
1878
  public abstract Static3D[] getRotationAxis();
1879
  public abstract int[][] getBasicAngles();
1880
  public abstract int getNumFaces();
1881
  public abstract String getObjectName();
1882
  public abstract String getInventor();
1883
  public abstract int getYearOfInvention();
1884
  public abstract int getComplexity();
1885
  public abstract int getFOV();
1886
  public abstract String[][] getTutorials();
1887
  }
(7-7/10)