Project

General

Profile

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

distorted-objectlib / src / main / java / org / distorted / objectlib / main / TwistyObject.java @ cf93ea4e

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(int meshState, int iconMode, Static4D quat, Static3D move, float scale, InitAssets assets)
162
    {
163
    try
164
      {
165
      InputStream jsonStream = assets!=null ? assets.getJsonStream(): null;
166
      InputStream meshStream = assets!=null ? assets.getMeshStream(): null;
167
      JsonReader reader = new JsonReader();
168
      reader.parseJsonFile(jsonStream);
169
      setReader(reader);
170
      mNumLayers = reader.getNumLayers();
171
      mSize      = reader.getSize();
172
      mInitData  = null;
173
      initialize(meshState,iconMode,quat,move,scale,meshStream,true);
174
      mError = false;
175
      mErrorString=null;
176
      }
177
    catch(Exception ex)
178
      {
179
      mError = true;
180
      mErrorString = ex.getMessage();
181
      }
182
    }
183

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

    
186
  public TwistyObject(int meshState, int iconMode, float size, Static4D quat, Static3D move, float scale, InitData data, InitAssets assets)
187
    {
188
    mNumLayers = data.getNumLayers();
189
    mSize      = size;
190
    mInitData  = data;
191
    InputStream meshStream = assets!=null ? assets.getMeshStream() : null;
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()+" "+quat.get3());
204
    problem += (" "+angle+" "+place+" "+tracking[0]+" "+tracking[1]+" "+tracking[2]);
205
    problem += (" "+axisX+" "+axisY+" "+axisZ)+" "+mPointNum;
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
    mRotationState = STATE_NOTHING;
215
    mIconMode = iconMode;
216
    mQuat = quat;
217
    mAxis = getRotationAxis();
218
    mInitScreenRatio = getScreenRatio();
219
    mBasicAngles = getBasicAngles();
220
    mObjectQuats = getQuats();
221
    mNumQuats = mObjectQuats.length;
222
    mOrigPos = getCubitPositions(mNumLayers);
223
    mNumPuzzleFaces = getNumFaces();
224
    mRowOffsets = new float[mNumPuzzleFaces][3];
225
    mTmp = new float[4];
226

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

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

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

    
245
    mScrambler = new ObjectScrambler(scramblingType,mNumAxis,mNumLayers,algorithms,edges);
246

    
247
    boolean bandaged=false;
248

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

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

    
264
    mRotationAngleStatic = new Static1D(0);
265
    mRotationAngleMiddle = new Static1D(0);
266
    mRotationAngleFinal  = new Static1D(0);
267

    
268
    mObjectScale = new Static3D(scale,scale,scale);
269
    setObjectRatioNow(scale,720);
270

    
271
    mEffects = new DistortedEffects();
272
    createQuaternionEffects();
273

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

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

    
283
    int index = getSolvedFunctionIndex();
284
    mSolved = new TwistyObjectSolved(this,mOrigPos,index);
285
    mSolved.setupSolvedQuats(getSolvedQuats());
286

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

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

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

    
297
  private void createQuaternionEffects()
298
    {
299
    if( mNumQuats<=ObjectControl.MAX_QUATS )
300
      {
301
      mIsInMixupMode = false;
302

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

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

    
329
///////////////////////////////////////////////////////////////////////////////////////////////////
330

    
331
  private Static3D getPos(float[] origPos)
332
    {
333
    int len = origPos.length/3;
334
    float sumX = 0.0f;
335
    float sumY = 0.0f;
336
    float sumZ = 0.0f;
337

    
338
    for(int i=0; i<len; i++)
339
      {
340
      sumX += origPos[3*i  ];
341
      sumY += origPos[3*i+1];
342
      sumZ += origPos[3*i+2];
343
      }
344

    
345
    sumX /= len;
346
    sumY /= len;
347
    sumZ /= len;
348

    
349
    return new Static3D(sumX,sumY,sumZ);
350
    }
351

    
352
///////////////////////////////////////////////////////////////////////////////////////////////////
353

    
354
  private void createOuterFaces()
355
    {
356
    for(int v=0; v<mNumCubitVariants; v++)
357
      {
358
      int[][] indices = mShapes[v].getVertIndices();
359
      int faces = indices.length;
360
      mVariantFaceIsOuter[v] = new int[faces];
361
      }
362

    
363
    for( int cubit=0; cubit<mNumCubits; cubit++)
364
      {
365
      int variant = getCubitVariant(cubit,mNumLayers);
366
      int[][] indices = mShapes[variant].getVertIndices();
367
      int numFaces = indices.length;
368

    
369
      for(int face=0; face<numFaces; face++)
370
        if( getCubitFaceColor(cubit,face)>=0 )
371
          {
372
          mVariantFaceIsOuter[variant][face] = 1;
373
          }
374
      }
375
    }
376

    
377
///////////////////////////////////////////////////////////////////////////////////////////////////
378

    
379
  private void getQuatsAndShapes(boolean fromDMESH, boolean fromJSON)
380
    {
381
    mNumCubitVariants = getNumCubitVariants(mNumLayers);
382

    
383
    if( !fromDMESH || !fromJSON )
384
      {
385
      FactoryCubit factory = FactoryCubit.getInstance();
386
      factory.clear();
387

    
388
      mOrigQuat = new Static4D[mNumCubits];
389
      for(int i=0; i<mNumCubits; i++) mOrigQuat[i] = getCubitQuats(i,mNumLayers);
390

    
391
      mShapes = new ObjectShape[mNumCubitVariants];
392
      for(int i=0; i<mNumCubitVariants; i++) mShapes[i] = getObjectShape(i);
393
      mNumCubitFaces = ObjectShape.computeNumComponents(mShapes);
394
      mVariantFaceIsOuter = new int[mNumCubitVariants][];
395

    
396
      if( !fromJSON )
397
        {
398
        mCubitFaceColors = ObjectShape.computeColors(mShapes,mOrigPos,mOrigQuat,this);
399
        createOuterFaces();
400
        }
401

    
402
      if( fromDMESH )
403
        {
404
        for(int i=0; i<mNumCubitVariants; i++) factory.createNewFaceTransform(mShapes[i], mVariantFaceIsOuter[i]);
405
        }
406
      }
407
    }
408

    
409
///////////////////////////////////////////////////////////////////////////////////////////////////
410

    
411
  private void createMeshAndCubits(InputStream stream, int meshState, boolean fromDMESH)
412
    {
413
    mCubits = new TwistyObjectCubit[mNumCubits];
414

    
415
    if( fromDMESH )
416
      {
417
      DataInputStream dos = new DataInputStream(stream);
418
      mMesh = new MeshFile(dos);
419

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

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

    
440
      mMesh = new MeshJoined(cubitMesh);
441

    
442
      float pillowCoeff = getPillowCoeff();
443

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

    
454
    for(int i=0; i<mNumCubits; i++)
455
      {
456
      mCubits[i] = new TwistyObjectCubit(this,mOrigPos[i],mNumAxis,mMaxNumLayers,i);
457
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
458
      }
459
    }
460

    
461
///////////////////////////////////////////////////////////////////////////////////////////////////
462

    
463
  private MeshBase createCubitMesh(int cubit, int[] numLayers, int meshState, int numComponents)
464
    {
465
    int variant = getCubitVariant(cubit,numLayers);
466

    
467
    if( mMeshes==null ) mMeshes = new MeshBase[mNumCubitVariants];
468

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

    
478
    MeshBase mesh = mMeshes[variant].copy(true);
479
    MatrixEffectQuaternion quat = new MatrixEffectQuaternion( mOrigQuat[cubit], CENTER );
480
    mesh.apply(quat,0xffffffff,0);
481

    
482
    return mesh;
483
    }
484

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

    
487
  private void setUpTextures(boolean fromDMESH, boolean fromJSON)
488
    {
489
    mTexture = new DistortedTexture();
490

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

    
506
    mStickerOverrides = getStickerOverrides();
507
    mNumOverrides = mStickerOverrides==null ? 0 : mStickerOverrides.length;
508

    
509
    mNumTextures= mNumFaceColors*mNumStickerTypes + mNumOverrides;
510
    mNumTexCols = NUM_STICKERS_IN_ROW;
511
    mNumTexRows = (mNumTextures+1)/NUM_STICKERS_IN_ROW;
512
    if( mNumTexCols*mNumTexRows < mNumTextures+1 ) mNumTexRows++;
513

    
514
    if( !fromDMESH || shouldResetTextureMaps() ) resetAllTextureMaps();
515
    else overrideCubitFaceColor();
516

    
517
    setTexture();
518
    }
519

    
520
///////////////////////////////////////////////////////////////////////////////////////////////////
521

    
522
  public InitData getInitData()
523
    {
524
    return mInitData;
525
    }
526

    
527
///////////////////////////////////////////////////////////////////////////////////////////////////
528

    
529
  public boolean isInIconMode()
530
    {
531
    return mIconMode==MODE_ICON;
532
    }
533

    
534
///////////////////////////////////////////////////////////////////////////////////////////////////
535

    
536
  public int getVariantStickerShape(int variant, int face)
537
    {
538
    return face>=mStickerVariants[variant].length ? -1 : mStickerVariants[variant][face];
539
    }
540

    
541
///////////////////////////////////////////////////////////////////////////////////////////////////
542

    
543
  public boolean shouldResetTextureMaps()
544
    {
545
    return false;
546
    }
547

    
548
///////////////////////////////////////////////////////////////////////////////////////////////////
549

    
550
  public int[][] getScrambleAlgorithms()
551
    {
552
    return ScrambleEdgeGenerator.getScramblingAlgorithms(mBasicAngles);
553
    }
554

    
555
///////////////////////////////////////////////////////////////////////////////////////////////////
556

    
557
  private boolean sticksOut(Static3D[] faceAxis, float[] dist, float x, float y, float z )
558
    {
559
    final float MAXERR = 0.05f;
560
    int numAxis = dist.length;
561

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

    
569
    return false;
570
    }
571

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

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

    
580
    for( float[] vertex : vertices)
581
      {
582
      float x = vertex[0];
583
      float y = vertex[1];
584
      float z = vertex[2];
585

    
586
      QuatHelper.rotateVectorByQuat(tmp, x, y, z, 1, quat);
587

    
588
      float mx = tmp[0] + px;
589
      float my = tmp[1] + py;
590
      float mz = tmp[2] + pz;
591

    
592
      if( sticksOut(axis, dist3D, mx,my,mz) ) return false;
593
      }
594

    
595
    return true;
596
    }
597

    
598
///////////////////////////////////////////////////////////////////////////////////////////////////
599

    
600
  private float computeAvg(float[] pos, int offset)
601
    {
602
    int len = pos.length/3;
603
    float ret=0.0f;
604
    for(int i=0; i<len; i++) ret += pos[3*i+offset];
605
    ret /= len;
606

    
607
    return ret;
608
    }
609

    
610
///////////////////////////////////////////////////////////////////////////////////////////////////
611

    
612
  protected void displayCubitQuats()
613
    {
614
    StringBuilder builder = new StringBuilder();
615
    float[] tmp = new float[4];
616
    float ERR = 0.01f;
617

    
618
    for(int cubit=0; cubit<mNumCubits; cubit++)
619
      {
620
      builder.append(cubit);
621
      builder.append(" : ");
622

    
623
      int refCubit,variant = getCubitVariant(cubit,mNumLayers);
624

    
625
      for(refCubit=0; refCubit<mNumCubits; refCubit++)
626
        {
627
        if( getCubitVariant(refCubit,mNumLayers)==variant ) break;
628
        }
629

    
630
      float[] curpos = mOrigPos[cubit];
631
      float[] refpos = mOrigPos[refCubit];
632
      float refX = computeAvg(refpos,0);
633
      float refY = computeAvg(refpos,1);
634
      float refZ = computeAvg(refpos,2);
635
      float curX = computeAvg(curpos,0);
636
      float curY = computeAvg(curpos,1);
637
      float curZ = computeAvg(curpos,2);
638

    
639
      for(int quat=0; quat<mNumQuats; quat++)
640
        {
641
        QuatHelper.rotateVectorByQuat(tmp,refX,refY,refZ,0,mObjectQuats[quat]);
642

    
643
        float dx = tmp[0]-curX;
644
        float dy = tmp[1]-curY;
645
        float dz = tmp[2]-curZ;
646

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

    
661
      builder.append('\n');
662
      }
663

    
664
    android.util.Log.e("D", "cubitQuats: \n"+builder );
665
    }
666

    
667
///////////////////////////////////////////////////////////////////////////////////////////////////
668

    
669
  public int getCubitRotationType(int cubit)
670
    {
671
    return TwistyObjectCubit.TYPE_NORMAL;
672
    }
673

    
674
///////////////////////////////////////////////////////////////////////////////////////////////////
675

    
676
  float[] getTrackingPoint(int cubitIndex, int cubitType)
677
    {
678
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
679
      {
680
      int variant = getCubitVariant(cubitIndex,mNumLayers);
681

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

    
704
      int[][] indices = mShapes[variant].getVertIndices();
705
      int outer=-1, faces = indices.length;
706

    
707
      for(int i=0; i<faces; i++)
708
        {
709
        if( mVariantFaceIsOuter[variant][i]==1 )
710
          {
711
          outer=i;
712
          break;
713
          }
714
        }
715

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

    
725
        ret[0] = mTmp[0]+computeAvg(curpos,0);
726
        ret[1] = mTmp[1]+computeAvg(curpos,1);
727
        ret[2] = mTmp[2]+computeAvg(curpos,2);
728

    
729
        return ret;
730
        }
731
      else
732
        {
733
        android.util.Log.e("D", "Error in getTrackingPoint: no outer face??");
734
        }
735
      }
736

    
737
    return null;
738
    }
739

    
740
///////////////////////////////////////////////////////////////////////////////////////////////////
741

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

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

    
761
      return -2;
762
      }
763

    
764
    return -1;
765
    }
766

    
767
///////////////////////////////////////////////////////////////////////////////////////////////////
768

    
769
  public float[] getCubitRowOffset(int cubitIndex)
770
    {
771
    return null;
772
    }
773

    
774
///////////////////////////////////////////////////////////////////////////////////////////////////
775

    
776
  void setRotationRowOffset(int puzzleFace, float[] offset)
777
    {
778
    mRowOffsets[puzzleFace][0] = offset[0];
779
    mRowOffsets[puzzleFace][1] = offset[1];
780
    mRowOffsets[puzzleFace][2] = offset[2];
781
    }
782

    
783
///////////////////////////////////////////////////////////////////////////////////////////////////
784

    
785
  int getNumAxis()
786
    {
787
    return mNumAxis;
788
    }
789

    
790
///////////////////////////////////////////////////////////////////////////////////////////////////
791

    
792
  public int[][] getSolvedQuats()
793
    {
794
    return mSolved.getSolvedQuats(mNumLayers,mNumCubitFaces,mCubitFaceColors);
795
    }
796

    
797
///////////////////////////////////////////////////////////////////////////////////////////////////
798

    
799
  public int getSolvedFunctionIndex()
800
    {
801
    return 0;
802
    }
803

    
804
///////////////////////////////////////////////////////////////////////////////////////////////////
805

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

    
816
    if( cubitType!=TwistyObjectCubit.TYPE_NORMAL )
817
      {
818
      xoff = mRowOffsets[puzzleFace][0];
819
      yoff = mRowOffsets[puzzleFace][1];
820
      zoff = mRowOffsets[puzzleFace][2];
821
      }
822

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

    
829
    return ret;
830
    }
831

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

    
834
  private int computeSingleRow(int axisIndex,float casted)
835
    {
836
    int num = mNumCuts[axisIndex];
837

    
838
    for(int i=0; i<num; i++)
839
      {
840
      if( casted<mCuts[axisIndex][i] ) return (1<<i);
841
      }
842

    
843
    return (1<<num);
844
    }
845

    
846
///////////////////////////////////////////////////////////////////////////////////////////////////
847

    
848
  private boolean wasRotateApplied()
849
    {
850
    return mEffects.exists(mRotateEffect.getID());
851
    }
852

    
853
///////////////////////////////////////////////////////////////////////////////////////////////////
854

    
855
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
856
    {
857
    return (mCubits[cubit].getRotRow(axis) & rowBitmap) != 0;
858
    }
859

    
860
///////////////////////////////////////////////////////////////////////////////////////////////////
861
// note the minus in front of the sin() - we rotate counterclockwise
862
// when looking towards the direction where the axis increases in values.
863

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

    
872
    return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
873
    }
874

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

    
877
  private synchronized void setupPosition(int[][] moves)
878
    {
879
    if( moves!=null )
880
      {
881
      Static4D quat;
882
      int index, axisIndex, row, rowBitmap, basic, angle;
883

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

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

    
911
        recomputeFaceOffsets();
912

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

    
930
///////////////////////////////////////////////////////////////////////////////////////////////////
931

    
932
  public int getScrambleType()
933
    {
934
    return ObjectScrambler.SCRAMBLING_ALGORITHMS;
935
    }
936

    
937
///////////////////////////////////////////////////////////////////////////////////////////////////
938

    
939
  int computeBitmapFromRow(int rowBitmap, int axis)
940
    {
941
    if( mIsBandaged )
942
      {
943
      int bitmap, initBitmap=0;
944

    
945
      while( initBitmap!=rowBitmap )
946
        {
947
        initBitmap = rowBitmap;
948

    
949
        for(int cubit=0; cubit<mNumCubits; cubit++)
950
          {
951
          bitmap = mCubits[cubit].getRotRow(axis);
952
          if( (rowBitmap & bitmap) != 0 ) rowBitmap |= bitmap;
953
          }
954
        }
955
      }
956

    
957
    return rowBitmap;
958
    }
959

    
960
///////////////////////////////////////////////////////////////////////////////////////////////////
961

    
962
  private int computeRowFromBitmap(int rowBitmap)
963
    {
964
    int index = 0;
965

    
966
    while(index<32)
967
      {
968
      if( (rowBitmap&0x1) != 0 ) return index;
969
      rowBitmap>>=1;
970
      index++;
971
      }
972
    return 0;
973
    }
974

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

    
979
  void clampPos(float[] pos, int offset)
980
    {
981
    float currError, minError = Float.MAX_VALUE;
982
    int minErrorIndex1 = -1;
983
    int minErrorIndex2 = -1;
984

    
985
    float x = pos[offset  ];
986
    float y = pos[offset+1];
987
    float z = pos[offset+2];
988

    
989
    float xo,yo,zo;
990

    
991
    for(int i=0; i<mNumCubits; i++)
992
      {
993
      int len = mOrigPos[i].length / 3;
994

    
995
      for(int j=0; j<len; j++)
996
        {
997
        xo = mOrigPos[i][3*j  ];
998
        yo = mOrigPos[i][3*j+1];
999
        zo = mOrigPos[i][3*j+2];
1000

    
1001
        currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
1002

    
1003
        if( currError<minError )
1004
          {
1005
          minError = currError;
1006
          minErrorIndex1 = i;
1007
          minErrorIndex2 = j;
1008
          }
1009
        }
1010
      }
1011

    
1012
    if( minError< 0.05f ) // TODO: 0.05 ?
1013
      {
1014
      pos[offset  ] = mOrigPos[minErrorIndex1][3*minErrorIndex2  ];
1015
      pos[offset+1] = mOrigPos[minErrorIndex1][3*minErrorIndex2+1];
1016
      pos[offset+2] = mOrigPos[minErrorIndex1][3*minErrorIndex2+2];
1017
      }
1018
    }
1019

    
1020
///////////////////////////////////////////////////////////////////////////////////////////////////
1021

    
1022
  private float getAngle()
1023
    {
1024
    mPointNum = mRotationAngle.getNumPoints();
1025
    return mPointNum>=1 ? mRotationAngle.getPoint(mPointNum-1).get0() : 0;
1026
    }
1027

    
1028
///////////////////////////////////////////////////////////////////////////////////////////////////
1029

    
1030
  void setLibInterface(ObjectLibInterface inter)
1031
    {
1032
    mInterface = inter;
1033
    }
1034

    
1035
///////////////////////////////////////////////////////////////////////////////////////////////////
1036

    
1037
  void applyScrambles(int[][] moves)
1038
    {
1039
    setupPosition(moves);
1040
    }
1041

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

    
1044
  void initializeObject(int[][] moves)
1045
    {
1046
    solve();
1047
    setupPosition(moves);
1048
    }
1049

    
1050
///////////////////////////////////////////////////////////////////////////////////////////////////
1051

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

    
1063
    mRotationAngleStatic.set0(0);
1064
    mRotationAngle.removeAll();
1065

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

    
1076
    recomputeFaceOffsets();
1077

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

    
1092
    mRotationState = STATE_NOTHING;
1093
    }
1094

    
1095
///////////////////////////////////////////////////////////////////////////////////////////////////
1096

    
1097
  private void recomputeFaceOffsets()
1098
    {
1099
    for(int i=0; i<mNumPuzzleFaces; i++)
1100
      {
1101
      mRowOffsets[i][0] =0;
1102
      mRowOffsets[i][1] =0;
1103
      mRowOffsets[i][2] =0;
1104
      }
1105

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

    
1117
///////////////////////////////////////////////////////////////////////////////////////////////////
1118

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

    
1129
      mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
1130
      mRotationAngle.resetToBeginning();
1131
      mRotationAngle.removeAll();
1132
      mRotationAngle.add(mRotationAngleStatic);
1133
      mRotationAngle.add(mRotationAngleMiddle);
1134
      mRotationAngle.add(mRotationAngleFinal);
1135
      mRotateEffect.notifyWhenFinished(listener);
1136

    
1137
      return mRotateEffect.getID();
1138
      }
1139

    
1140
    return 0;
1141
    }
1142

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

    
1145
  synchronized long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
1146
    {
1147
    int mult = 1;
1148

    
1149
    if( wasRotateApplied() )
1150
      {
1151
      if( mRotationState==STATE_ROTATE )
1152
        {
1153
        return 0;
1154
        }
1155
      if( mRotationState==STATE_FINISH )
1156
        {
1157
        removeRotationNow();
1158
        mult = -1;
1159
        }
1160

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

    
1175
    return 0;
1176
    }
1177

    
1178
///////////////////////////////////////////////////////////////////////////////////////////////////
1179

    
1180
  void continueRotation(float angleInDegrees)
1181
    {
1182
    mRotationAngleStatic.set0(angleInDegrees);
1183
    }
1184

    
1185
///////////////////////////////////////////////////////////////////////////////////////////////////
1186

    
1187
  synchronized boolean beginNewRotation(int axis, int row )
1188
    {
1189
    if( mRotationState==STATE_ROTATE )
1190
      {
1191
      return false;
1192
      }
1193
    if( mRotationState==STATE_FINISH )
1194
      {
1195
      removeRotationNow();
1196
      }
1197

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

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

    
1217
    return true;
1218
    }
1219

    
1220
///////////////////////////////////////////////////////////////////////////////////////////////////
1221

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

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

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

    
1239
  private int getCubitFaceColor(int cubit, int face)
1240
    {
1241
    int puzzleFace = getCubitFaceMap(cubit,face);
1242
    if( puzzleFace>=0 ) puzzleFace %= mNumFaceColors;
1243
    return puzzleFace;
1244
    }
1245

    
1246
///////////////////////////////////////////////////////////////////////////////////////////////////
1247

    
1248
  public int getCubitFaceMap(int cubit, int face)
1249
    {
1250
    int numFaces = mCubitFaceColors[cubit].length;
1251
    int puzzleFace = face<numFaces ? mCubitFaceColors[cubit][face] : -1;
1252
    return puzzleFace<0 ? -1 : puzzleFace;
1253
    }
1254

    
1255
///////////////////////////////////////////////////////////////////////////////////////////////////
1256

    
1257
  void resetAllTextureMaps()
1258
    {
1259
    final float ratioW = 1.0f/mNumTexCols;
1260
    final float ratioH = 1.0f/mNumTexRows;
1261
    int cubColor, stiShape, texIndex, variant, row, col;
1262

    
1263
    for(int cubit=0; cubit<mNumCubits; cubit++)
1264
      {
1265
      final Static4D[] maps = new Static4D[mNumCubitFaces];
1266
      variant = getCubitVariant(cubit,mNumLayers);
1267

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

    
1276
        maps[face] = new Static4D( col*ratioW, row*ratioH, ratioW, ratioH);
1277
        }
1278

    
1279
      mMesh.setTextureMap(maps,mNumCubitFaces*cubit);
1280
      }
1281

    
1282
    overrideCubitFaceColor();
1283
    }
1284

    
1285
///////////////////////////////////////////////////////////////////////////////////////////////////
1286

    
1287
  private void overrideCubitFaceColor()
1288
    {
1289
    final float ratioW = 1.0f/mNumTexCols;
1290
    final float ratioH = 1.0f/mNumTexRows;
1291

    
1292
    for(int i=0; i<mNumOverrides; i++)
1293
      {
1294
      int[] cubitFaces = mStickerOverrides[i].getCubitFaces();
1295
      int length = cubitFaces.length/2;
1296

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

    
1311
///////////////////////////////////////////////////////////////////////////////////////////////////
1312

    
1313
  void releaseResources()
1314
    {
1315
    mTexture.markForDeletion();
1316
    mMesh.markForDeletion();
1317
    mEffects.markForDeletion();
1318

    
1319
    for(int j=0; j<mNumCubits; j++)
1320
      {
1321
      mCubits[j].releaseResources();
1322
      }
1323
    }
1324

    
1325
///////////////////////////////////////////////////////////////////////////////////////////////////
1326

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

    
1341
///////////////////////////////////////////////////////////////////////////////////////////////////
1342

    
1343
  synchronized void restorePreferences(SharedPreferences preferences)
1344
    {
1345
    boolean error = false;
1346
    String key = getShortName();
1347

    
1348
    for(int i=0; i<mNumCubits; i++)
1349
      {
1350
      mQuatDebug[i] = mCubits[i].restorePreferences(key,preferences);
1351

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

    
1360
    if( !error )
1361
      {
1362
      recomputeFaceOffsets();
1363

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

    
1375
    if( error )
1376
      {
1377
      for(int i=0; i<mNumCubits; i++)
1378
        {
1379
        mCubits[i].solve();
1380
        setCubitQuat(i,mCubits[i].computeAssociation(),0);
1381
        }
1382
      }
1383
    }
1384

    
1385
///////////////////////////////////////////////////////////////////////////////////////////////////
1386

    
1387
  void savePreferences(SharedPreferences.Editor editor)
1388
    {
1389
    String key = getShortName();
1390
    for(int i=0; i<mNumCubits; i++) mCubits[i].savePreferences(key,editor);
1391
    }
1392

    
1393
///////////////////////////////////////////////////////////////////////////////////////////////////
1394

    
1395
  public void removePreferences(SharedPreferences.Editor editor)
1396
    {
1397
    String key = getShortName();
1398
    for(int i=0; i<mNumCubits; i++) mCubits[i].removePreferences(key,editor);
1399
    }
1400

    
1401
///////////////////////////////////////////////////////////////////////////////////////////////////
1402

    
1403
  private float computeRadiusCorrection(float[] sticker, int curr, int len)
1404
    {
1405
    final float A = 0.8f;  // 0<A<1
1406

    
1407
    int prev = curr>0 ? curr-1 : len-1;
1408
    int next = curr<len-1 ? curr+1 : 0;
1409

    
1410
    float v1x = sticker[2*prev  ]-sticker[2*curr  ];
1411
    float v1y = sticker[2*prev+1]-sticker[2*curr+1];
1412
    float v2x = sticker[2*next  ]-sticker[2*curr  ];
1413
    float v2y = sticker[2*next+1]-sticker[2*curr+1];
1414

    
1415
    float len1= v1x*v1x+v1y*v1y;
1416
    float len2= v2x*v2x+v2y*v2y;
1417

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

    
1420
    return 1-A*cos;
1421
    }
1422

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

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

    
1442
    return 0.0f;
1443
    }
1444

    
1445
///////////////////////////////////////////////////////////////////////////////////////////////////
1446

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

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

    
1469
    return mStickers[sticker];
1470
    }
1471

    
1472
///////////////////////////////////////////////////////////////////////////////////////////////////
1473
// some objects (currently Kilominx,Ivy,Rex) might want to change the stickers.
1474

    
1475
  public void adjustStickerCoords()
1476
    {
1477

    
1478
    }
1479

    
1480
///////////////////////////////////////////////////////////////////////////////////////////////////
1481

    
1482
  public Static4D[] getQuats()
1483
    {
1484
    if( mObjectQuats==null )
1485
      {
1486
      mObjectQuats = QuatGroupGenerator.computeGroup(mAxis,mBasicAngles);
1487
      }
1488

    
1489
    return mObjectQuats;
1490
    }
1491

    
1492
///////////////////////////////////////////////////////////////////////////////////////////////////
1493

    
1494
  public int[][] getVariantFaceIsOuter()
1495
    {
1496
    return mVariantFaceIsOuter;
1497
    }
1498

    
1499
///////////////////////////////////////////////////////////////////////////////////////////////////
1500

    
1501
  public int getInternalColor()
1502
    {
1503
    return COLOR_INTERNAL;
1504
    }
1505

    
1506
///////////////////////////////////////////////////////////////////////////////////////////////////
1507
// the getFaceColors + final INTERNAL_COLOR in a grid (so that we do not exceed the maximum texture size)
1508

    
1509
  private void createTexture()
1510
    {
1511
    Paint paint = new Paint();
1512
    mBitmap = Bitmap.createBitmap( mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, Bitmap.Config.ARGB_4444);
1513
    Canvas canvas = new Canvas(mBitmap);
1514

    
1515
    paint.setAntiAlias(true);
1516
    paint.setTextAlign(Paint.Align.CENTER);
1517
    paint.setStyle(Paint.Style.FILL);
1518
    paint.setColor(getInternalColor());
1519
    canvas.drawRect(0, 0, mNumTexCols*TEXTURE_HEIGHT, mNumTexRows*TEXTURE_HEIGHT, paint);
1520

    
1521
    int texture = 0;
1522
    FactorySticker factory = FactorySticker.getInstance();
1523

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

    
1539
        texture++;
1540
        }
1541
    }
1542

    
1543
///////////////////////////////////////////////////////////////////////////////////////////////////
1544

    
1545
  void setTexture()
1546
    {
1547
    if( mBitmap==null ) createTexture();
1548

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

    
1556
///////////////////////////////////////////////////////////////////////////////////////////////////
1557

    
1558
  void setObjectRatioNow(float sc, int nodeSize)
1559
    {
1560
    mObjectScreenRatio = sc;
1561
    float scale = mObjectScreenRatio*mInitScreenRatio*nodeSize/mSize;
1562
    mObjectScale.set(scale,scale,scale);
1563

    
1564
    if( mTouchControl ==null ) mTouchControl = getTouchControl();
1565
    mTouchControl.setObjectRatio(mObjectScreenRatio*mInitScreenRatio);
1566
    }
1567

    
1568
///////////////////////////////////////////////////////////////////////////////////////////////////
1569

    
1570
  void setObjectRatio(float sizeChange, int nodeSize)
1571
    {
1572
    mObjectScreenRatio *= (1.0f+sizeChange)/2;
1573

    
1574
    if( mObjectScreenRatio>MAX_SIZE_CHANGE) mObjectScreenRatio = MAX_SIZE_CHANGE;
1575
    if( mObjectScreenRatio<MIN_SIZE_CHANGE) mObjectScreenRatio = MIN_SIZE_CHANGE;
1576

    
1577
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1578
    }
1579

    
1580
///////////////////////////////////////////////////////////////////////////////////////////////////
1581

    
1582
  void setNodeSize(int nodeSize)
1583
    {
1584
    setObjectRatioNow(mObjectScreenRatio, nodeSize);
1585
    }
1586

    
1587
///////////////////////////////////////////////////////////////////////////////////////////////////
1588

    
1589
  public float getRatio()
1590
    {
1591
    return mObjectScreenRatio;
1592
    }
1593

    
1594
///////////////////////////////////////////////////////////////////////////////////////////////////
1595

    
1596
  public float getObjectRatio()
1597
    {
1598
    return mObjectScreenRatio*mInitScreenRatio;
1599
    }
1600

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

    
1603
  boolean isSolved()
1604
    {
1605
    return mSolved.isSolved(mCubits);
1606
    }
1607

    
1608
///////////////////////////////////////////////////////////////////////////////////////////////////
1609

    
1610
  int computeNearestAngle(int basicAngle, float angle, float speed)
1611
    {
1612
    int nearestAngle = 360/basicAngle;
1613
    int tmp = (int)((angle+nearestAngle/2)/nearestAngle);
1614
    if( angle< -(nearestAngle*0.5) ) tmp-=1;
1615

    
1616
    if( tmp!=0 ) return nearestAngle*tmp;
1617

    
1618
    return speed> 1.2f ? nearestAngle*(angle>0 ? 1:-1) : 0;
1619
    }
1620

    
1621
///////////////////////////////////////////////////////////////////////////////////////////////////
1622
// INTERNAL API - those are called from 'effects' package
1623
///////////////////////////////////////////////////////////////////////////////////////////////////
1624

    
1625
  public void randomizeNewScramble(int[][] scramble, Random rnd, int curr, int total)
1626
    {
1627
    mScrambler.randomizeNewScramble(scramble,rnd,curr,total, getSignature() );
1628
    }
1629

    
1630
///////////////////////////////////////////////////////////////////////////////////////////////////
1631

    
1632
  public Static4D getRotationQuat()
1633
    {
1634
    return mQuat;
1635
    }
1636

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

    
1639
  public float getSize()
1640
    {
1641
    return mSize;
1642
    }
1643

    
1644
///////////////////////////////////////////////////////////////////////////////////////////////////
1645

    
1646
  public void applyEffect(Effect effect, int position)
1647
    {
1648
    mEffects.apply(effect, position);
1649
    }
1650

    
1651
///////////////////////////////////////////////////////////////////////////////////////////////////
1652

    
1653
  public void removeEffect(long effectID)
1654
    {
1655
    mEffects.abortById(effectID);
1656
    }
1657

    
1658
///////////////////////////////////////////////////////////////////////////////////////////////////
1659

    
1660
  public MeshBase getObjectMesh()
1661
    {
1662
    return mMesh;
1663
    }
1664

    
1665
///////////////////////////////////////////////////////////////////////////////////////////////////
1666

    
1667
  public DistortedEffects getObjectEffects()
1668
    {
1669
    return mEffects;
1670
    }
1671

    
1672
///////////////////////////////////////////////////////////////////////////////////////////////////
1673

    
1674
  public int getCubitType(int cubit)
1675
    {
1676
    return mCubits[cubit].getType();
1677
    }
1678

    
1679
///////////////////////////////////////////////////////////////////////////////////////////////////
1680

    
1681
  public float[] getCubitOffset(int cubit)
1682
    {
1683
    return mCubits[cubit].getOffset();
1684
    }
1685

    
1686
///////////////////////////////////////////////////////////////////////////////////////////////////
1687

    
1688
  public ObjectStickerOverride[] getStickerOverrides()
1689
    {
1690
    return null;
1691
    }
1692

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

    
1695
  public boolean getError()
1696
    {
1697
    return mError;
1698
    }
1699

    
1700
///////////////////////////////////////////////////////////////////////////////////////////////////
1701

    
1702
  public String getErrorString()
1703
    {
1704
    return mErrorString;
1705
    }
1706

    
1707
///////////////////////////////////////////////////////////////////////////////////////////////////
1708
// PUBLIC API
1709
///////////////////////////////////////////////////////////////////////////////////////////////////
1710

    
1711
  public int getCubitFaceStickerIndex(int cubit, int face)
1712
    {
1713
    Static4D texMap = mMesh.getTextureMap(mNumCubitFaces*cubit + face);
1714

    
1715
    int x = (int)(texMap.get0()/texMap.get2());
1716
    int y = (int)(texMap.get1()/texMap.get3());
1717

    
1718
    return (mNumTexRows-1-y)*NUM_STICKERS_IN_ROW + x;
1719
    }
1720

    
1721
///////////////////////////////////////////////////////////////////////////////////////////////////
1722

    
1723
  public int[] getNumLayers()
1724
    {
1725
    return mNumLayers;
1726
    }
1727

    
1728
///////////////////////////////////////////////////////////////////////////////////////////////////
1729

    
1730
  public synchronized void solve()
1731
    {
1732
    for(int i=0; i<mNumCubits; i++)
1733
      {
1734
      mCubits[i].solve();
1735
      }
1736

    
1737
    recomputeFaceOffsets();
1738

    
1739
    for(int i=0; i<mNumCubits; i++)
1740
      {
1741
      mCubits[i].computeRotationRow();
1742
      setCubitQuat(i,mCubits[i].computeAssociation(),0);
1743
      }
1744
    }
1745

    
1746
///////////////////////////////////////////////////////////////////////////////////////////////////
1747

    
1748
  public int getCubitQuatIndex(int cubit)
1749
    {
1750
    return (cubit>=0 && cubit<mNumCubits) ? mCubits[cubit].mQuatIndex : 0;
1751
    }
1752

    
1753
///////////////////////////////////////////////////////////////////////////////////////////////////
1754

    
1755
  public int getCubitRotRow(int cubit, int axis)
1756
    {
1757
    return mCubits[cubit].getRotRow(axis);
1758
    }
1759

    
1760
///////////////////////////////////////////////////////////////////////////////////////////////////
1761

    
1762
  public Bitmap getStickerBitmap()
1763
    {
1764
    return mBitmap;
1765
    }
1766

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

    
1769
  public DistortedNode getNode()
1770
    {
1771
    return mNode;
1772
    }
1773

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

    
1776
  public int getNumStickerTypes()
1777
    {
1778
    return mNumStickerTypes;
1779
    }
1780

    
1781
///////////////////////////////////////////////////////////////////////////////////////////////////
1782

    
1783
  public String reportState()
1784
    {
1785
    StringBuilder builder = new StringBuilder();
1786

    
1787
    for(int i=0; i<mNumCubits; i++ )
1788
      {
1789
      if( i>0 ) builder.append('.');
1790
      builder.append(mCubits[i].mQuatIndex);
1791
      }
1792

    
1793
    return builder.toString();
1794
    }
1795

    
1796
///////////////////////////////////////////////////////////////////////////////////////////////////
1797
// this is here only so it can be overridden in TwistyJSON so that we can get this from JSON.
1798

    
1799
  public int getNumCubitFaces()
1800
    {
1801
    return 0;
1802
    }
1803

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

    
1809
  public float getPillowCoeff()
1810
    {
1811
    return 1.0f;
1812
    }
1813

    
1814
///////////////////////////////////////////////////////////////////////////////////////////////////
1815

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

    
1848
///////////////////////////////////////////////////////////////////////////////////////////////////
1849

    
1850
  protected void setReader(JsonReader reader)
1851
    {
1852
    // empty
1853
    }
1854

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

    
1881
  // not only for JSON
1882
  public abstract Static3D[] getRotationAxis();
1883
  public abstract int[][] getBasicAngles();
1884
  public abstract int getNumFaces();
1885
  public abstract String getObjectName();
1886
  public abstract String getInventor();
1887
  public abstract int getYearOfInvention();
1888
  public abstract int getComplexity();
1889
  public abstract int getFOV();
1890
  public abstract String[][] getTutorials();
1891
  }
(8-8/11)