Project

General

Profile

Download (20.7 KB) Statistics
| Branch: | Tag: | Revision:

magiccube / src / main / java / org / distorted / objects / RubikObject.java @ 8cccfb10

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2020 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is free software: you can redistribute it and/or modify                            //
7
// it under the terms of the GNU General Public License as published by                          //
8
// the Free Software Foundation, either version 2 of the License, or                             //
9
// (at your option) any later version.                                                           //
10
//                                                                                               //
11
// Magic Cube is distributed in the hope that it will be useful,                                 //
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
14
// GNU General Public License for more details.                                                  //
15
//                                                                                               //
16
// You should have received a copy of the GNU General Public License                             //
17
// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
18
///////////////////////////////////////////////////////////////////////////////////////////////////
19

    
20
package org.distorted.objects;
21

    
22
import android.content.SharedPreferences;
23
import android.graphics.Bitmap;
24
import android.graphics.Canvas;
25
import android.graphics.Paint;
26

    
27
import com.google.firebase.crashlytics.FirebaseCrashlytics;
28

    
29
import org.distorted.library.effect.Effect;
30
import org.distorted.library.effect.MatrixEffectMove;
31
import org.distorted.library.effect.MatrixEffectQuaternion;
32
import org.distorted.library.effect.MatrixEffectScale;
33
import org.distorted.library.effect.VertexEffectRotate;
34
import org.distorted.library.main.DistortedEffects;
35
import org.distorted.library.main.DistortedNode;
36
import org.distorted.library.main.DistortedTexture;
37
import org.distorted.library.mesh.MeshBase;
38
import org.distorted.library.mesh.MeshJoined;
39
import org.distorted.library.mesh.MeshRectangles;
40
import org.distorted.library.message.EffectListener;
41
import org.distorted.library.type.Dynamic1D;
42
import org.distorted.library.type.Static1D;
43
import org.distorted.library.type.Static3D;
44
import org.distorted.library.type.Static4D;
45

    
46
///////////////////////////////////////////////////////////////////////////////////////////////////
47

    
48
public abstract class RubikObject extends DistortedNode
49
  {
50
  private static final Static3D CENTER = new Static3D(0,0,0);
51
  static final int INTERIOR_COLOR = 0xff000000;
52
  public static final int NODE_FBO_SIZE = 600;
53
  private static final int POST_ROTATION_MILLISEC = 500;
54
  private static final int TEXTURE_HEIGHT = 128;
55

    
56
  final float[] LEGAL_QUATS;
57
  final Static3D[] ROTATION_AXIS;
58
  final int NUM_FACES;
59

    
60
  static float OBJECT_SCREEN_RATIO;
61

    
62
  private final int NUM_CUBITS;
63
  private int mRotRowBitmap;
64
  private int mRotAxis;
65
  private Static3D[] mOrigPos;
66
  private Static3D mNodeScale;
67
  private Static4D mQuatAccumulated;
68
  private Cubit[] mCubits;
69
  private int mSize;
70
  private RubikObjectList mList;
71
  private MeshBase mMesh;
72
  private DistortedEffects mEffects;
73
  private VertexEffectRotate mRotateEffect;
74
  private Dynamic1D mRotationAngle;
75
  private Static3D mRotationAxis;
76

    
77
  float mStart, mStep;
78

    
79
  Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
80
  DistortedTexture mTexture;
81

    
82
  MatrixEffectScale mScaleEffect;
83
  MatrixEffectQuaternion mQuatCEffect;
84
  MatrixEffectQuaternion mQuatAEffect;
85

    
86
///////////////////////////////////////////////////////////////////////////////////////////////////
87

    
88
  RubikObject(int size, int fov, Static4D quatCur, Static4D quatAcc, DistortedTexture nodeTexture,
89
              MeshRectangles nodeMesh, DistortedEffects nodeEffects, int[][] moves, RubikObjectList list)
90
    {
91
    super(nodeTexture,nodeEffects,nodeMesh);
92

    
93
    resizeFBO(NODE_FBO_SIZE, NODE_FBO_SIZE);
94

    
95
    mList = list;
96
    mOrigPos = getCubitPositions(size);
97

    
98
    LEGAL_QUATS = getLegalQuats();
99
    NUM_CUBITS  = mOrigPos.length;
100
    ROTATION_AXIS = getRotationAxis();
101
    OBJECT_SCREEN_RATIO = getScreenRatio();
102
    NUM_FACES = getNumFaces();
103

    
104
    mSize = size;
105
    computeStartAndStep(mOrigPos);
106
    mNodeScale= new Static3D(1,1,1);
107
    mQuatAccumulated = quatAcc;
108

    
109
    mRotationAngle= new Dynamic1D();
110
    mRotationAxis = new Static3D(1,0,0);
111
    mRotateEffect = new VertexEffectRotate(mRotationAngle, mRotationAxis, CENTER);
112

    
113
    mRotationAngleStatic = new Static1D(0);
114
    mRotationAngleMiddle = new Static1D(0);
115
    mRotationAngleFinal  = new Static1D(0);
116

    
117
    float scale = OBJECT_SCREEN_RATIO*NODE_FBO_SIZE/mSize;
118
    mScaleEffect = new MatrixEffectScale(new Static3D(scale,scale,scale));
119
    mQuatCEffect = new MatrixEffectQuaternion(quatCur, CENTER);
120
    mQuatAEffect = new MatrixEffectQuaternion(quatAcc, CENTER);
121

    
122
    MatrixEffectScale nodeScaleEffect = new MatrixEffectScale(mNodeScale);
123
    nodeEffects.apply(nodeScaleEffect);
124

    
125
    mCubits = new Cubit[NUM_CUBITS];
126
    mTexture = new DistortedTexture();
127
    MeshBase[] cubitMesh = new MeshBase[NUM_CUBITS];
128

    
129
    for(int i=0; i<NUM_CUBITS; i++)
130
      {
131
      mCubits[i] = new Cubit(this,mOrigPos[i]);
132
      cubitMesh[i] = createCubitMesh(i);
133
      cubitMesh[i].apply(new MatrixEffectMove(mOrigPos[i]),1,0);
134
      cubitMesh[i].setEffectAssociation(0, mCubits[i].computeAssociation(), i);
135
      }
136

    
137
    mMesh = new MeshJoined(cubitMesh);
138

    
139
    resetAllTextureMaps();
140

    
141
    mEffects = new DistortedEffects();
142
    mEffects.apply(mRotateEffect);
143
    mEffects.apply(mQuatAEffect);
144
    mEffects.apply(mQuatCEffect);
145
    mEffects.apply(mScaleEffect);
146

    
147
    attach( new DistortedNode(mTexture,mEffects,mMesh) );
148

    
149
    setupPosition(moves);
150

    
151
    setProjection(fov, 0.1f);
152
    }
153

    
154
///////////////////////////////////////////////////////////////////////////////////////////////////
155

    
156
  private void textureCubitMesh(MeshBase mesh, int cubit)
157
    {
158
    boolean belongs;
159
    final Static4D[] maps = new Static4D[NUM_FACES];
160
    final float ratio = 1.0f/(NUM_FACES+1);
161

    
162
    if( 2*ROTATION_AXIS.length == NUM_FACES )  // i.e. there are faces on both ends of the axis (cube)
163
      {
164
      for(int i=0; i<NUM_FACES; i++)
165
        {
166
        belongs = isOnFace(cubit, i/2, i%2==0 ? mSize-1:0 );
167
        maps[i] = new Static4D( (belongs?i:NUM_FACES)*ratio, 0.0f, ratio, 1.0f);
168
        }
169
      }
170
    else if( ROTATION_AXIS.length == NUM_FACES )  // just a single face on the right end of an axis (pyraminx)
171
      {
172
      for(int i=0; i<NUM_FACES; i++)
173
        {
174
        belongs = isOnFace(cubit, i, 0 );
175
        maps[i] = new Static4D( (belongs?i:NUM_FACES)*ratio, 0.0f, ratio, 1.0f);
176
        }
177
      }
178

    
179
    mesh.setTextureMap(maps,NUM_FACES*cubit);
180
    }
181

    
182
///////////////////////////////////////////////////////////////////////////////////////////////////
183
// Cast centers of all Cubits on the first rotation Axis and compute the leftmost and rightmost
184
// one. From there compute the 'start' (i.e. the leftmost) and 'step' (i.e. distance between two
185
// consecutive).
186
// it is assumed that other rotation axis have the same 'start' and 'step' - this is the case with
187
// the Cube and the Pyraminx.
188
// Start and Step are then needed to compute which rotation row (with respect to a given axis) a
189
// given Cubit belongs to.
190

    
191
  private void computeStartAndStep(Static3D[] pos)
192
    {
193
    float min = Float.MAX_VALUE;
194
    float max = Float.MIN_VALUE;
195
    float axisX = ROTATION_AXIS[0].get0();
196
    float axisY = ROTATION_AXIS[0].get1();
197
    float axisZ = ROTATION_AXIS[0].get2();
198
    float tmp;
199

    
200
    for(int i=0; i<NUM_CUBITS; i++)
201
      {
202
      tmp = pos[i].get0()*axisX + pos[i].get1()*axisY + pos[i].get2()*axisZ;
203
      if( tmp<min ) min=tmp;
204
      if( tmp>max ) max=tmp;
205
      }
206

    
207
    mStart = min;
208
    mStep  = (max-min+1.0f)/mSize;
209
    }
210

    
211
///////////////////////////////////////////////////////////////////////////////////////////////////
212

    
213
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
214
    {
215
    int cubitRow = (int)(mCubits[cubit].mRotationRow[axis]+0.5f);
216
    return ((1<<cubitRow)&rowBitmap)!=0;
217
    }
218

    
219
///////////////////////////////////////////////////////////////////////////////////////////////////
220
// we cannot use belongsToRotation for deciding if to texture a face. Counterexample: the 'rotated'
221
// tetrahedrons of Pyraminx nearby the edge: they belong to rotation but their face which is rotated
222
// away from the face of the Pyraminx shouldn't be textured.
223

    
224
  private boolean isOnFace( int cubit, int axis, int row)
225
    {
226
    final float MAX_ERROR = 0.0001f;
227
    float diff = mCubits[cubit].mRotationRow[axis] - row;
228
    return diff*diff < MAX_ERROR;
229
    }
230

    
231
///////////////////////////////////////////////////////////////////////////////////////////////////
232
// note the minus in front of the sin() - we rotate counterclockwise
233
// when looking towards the direction where the axis increases in values.
234

    
235
  private Static4D makeQuaternion(int axisIndex, int angleInDegrees)
236
    {
237
    Static3D axis = ROTATION_AXIS[axisIndex];
238

    
239
    while( angleInDegrees<0 ) angleInDegrees += 360;
240
    angleInDegrees %= 360;
241
    
242
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
243
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
244

    
245
    return new Static4D(axis.get0()*sinA, axis.get1()*sinA, axis.get2()*sinA, cosA);
246
    }
247

    
248
///////////////////////////////////////////////////////////////////////////////////////////////////
249

    
250
  private void setupPosition(int[][] moves)
251
    {
252
    if( moves!=null )
253
      {
254
      Static4D quat;
255
      int axis, rowBitmap, angle;
256
      int corr = (360/getBasicAngle());
257

    
258
      for(int[] move: moves)
259
        {
260
        axis     = move[0];
261
        rowBitmap= move[1];
262
        angle    = move[2]*corr;
263
        quat     = makeQuaternion(axis,angle);
264

    
265
        for(int j=0; j<NUM_CUBITS; j++)
266
          if( belongsToRotation(j,axis,rowBitmap) )
267
            {
268
            mCubits[j].removeRotationNow(quat);
269
            }
270
        }
271
      }
272
    }
273

    
274
///////////////////////////////////////////////////////////////////////////////////////////////////
275

    
276
  int getCubitFaceColorIndex(int cubit, int face)
277
    {
278
    Static4D texMap = mMesh.getTextureMap(NUM_FACES*cubit + face);
279
    return (int)(texMap.get0() / texMap.get2());
280
    }
281

    
282
///////////////////////////////////////////////////////////////////////////////////////////////////
283
// Clamp all rotated positions to one of those original ones to avoid accumulating errors.
284

    
285
  void clampPos(Static3D pos)
286
    {
287
    float currError, minError = Float.MAX_VALUE;
288
    int minErrorIndex= -1;
289
    float x = pos.get0();
290
    float y = pos.get1();
291
    float z = pos.get2();
292
    float xo,yo,zo;
293

    
294
    for(int i=0; i<NUM_CUBITS; i++)
295
      {
296
      xo = mOrigPos[i].get0();
297
      yo = mOrigPos[i].get1();
298
      zo = mOrigPos[i].get2();
299

    
300
      currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
301

    
302
      if( currError<minError )
303
        {
304
        minError = currError;
305
        minErrorIndex = i;
306
        }
307
      }
308

    
309
    pos.set( mOrigPos[minErrorIndex] );
310
    }
311

    
312
///////////////////////////////////////////////////////////////////////////////////////////////////
313
// the getFaceColors + final black in a horizontal strip.
314

    
315
  public void createTexture()
316
    {
317
    Bitmap bitmap;
318

    
319
    Paint paint = new Paint();
320
    bitmap = Bitmap.createBitmap( (NUM_FACES+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
321
    Canvas canvas = new Canvas(bitmap);
322

    
323
    paint.setAntiAlias(true);
324
    paint.setTextAlign(Paint.Align.CENTER);
325
    paint.setStyle(Paint.Style.FILL);
326

    
327
    paint.setColor(INTERIOR_COLOR);
328
    canvas.drawRect(0, 0, (NUM_FACES+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, paint);
329

    
330
    for(int i=0; i<NUM_FACES; i++)
331
      {
332
      createFaceTexture(canvas, paint, i, i*TEXTURE_HEIGHT, 0, TEXTURE_HEIGHT);
333
      }
334

    
335
    mTexture.setTexture(bitmap);
336
    }
337

    
338
///////////////////////////////////////////////////////////////////////////////////////////////////
339

    
340
  public int getSize()
341
    {
342
    return mSize;
343
    }
344

    
345
///////////////////////////////////////////////////////////////////////////////////////////////////
346

    
347
  public void continueRotation(float angleInDegrees)
348
    {
349
    mRotationAngleStatic.set0(angleInDegrees);
350
    }
351

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

    
354
  public Static4D getRotationQuat()
355
      {
356
      return mQuatAccumulated;
357
      }
358

    
359
///////////////////////////////////////////////////////////////////////////////////////////////////
360

    
361
  public void recomputeScaleFactor(int scrWidth, int scrHeight)
362
    {
363
    float factor = Math.min(scrWidth,scrHeight);
364
    mNodeScale.set(factor,factor,factor);
365
    }
366

    
367
///////////////////////////////////////////////////////////////////////////////////////////////////
368

    
369
  public void savePreferences(SharedPreferences.Editor editor)
370
    {
371
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].savePreferences(editor);
372
    }
373

    
374
///////////////////////////////////////////////////////////////////////////////////////////////////
375

    
376
  public void restorePreferences(SharedPreferences preferences)
377
    {
378
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].restorePreferences(preferences);
379
    }
380

    
381
///////////////////////////////////////////////////////////////////////////////////////////////////
382

    
383
  public void releaseResources()
384
    {
385
    mTexture.markForDeletion();
386

    
387
    // TODO ?
388
    }
389

    
390
///////////////////////////////////////////////////////////////////////////////////////////////////
391

    
392
  public void apply(Effect effect, int position)
393
    {
394
    mEffects.apply(effect, position);
395
    }
396

    
397
///////////////////////////////////////////////////////////////////////////////////////////////////
398

    
399
  public void remove(long effectID)
400
    {
401
    mEffects.abortById(effectID);
402
    }
403

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

    
406
  public void solve()
407
    {
408
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].solve();
409
    }
410

    
411
///////////////////////////////////////////////////////////////////////////////////////////////////
412

    
413
  public boolean isSolved()
414
    {
415
    Static4D q = mCubits[0].mQuatScramble;
416

    
417
    for(int i=1; i<NUM_CUBITS; i++)
418
      {
419
      if( !mCubits[i].thereIsNoVisibleDifference(q) ) return false;
420
      }
421

    
422
    return true;
423
    }
424

    
425
///////////////////////////////////////////////////////////////////////////////////////////////////
426

    
427
  public void resetAllTextureMaps()
428
    {
429
    for(int i=0; i<NUM_CUBITS; i++)
430
      {
431
      textureCubitMesh( mMesh , i );
432
      }
433
    }
434

    
435
///////////////////////////////////////////////////////////////////////////////////////////////////
436

    
437
  public void setTextureMap(int cubit, int face, int newColor)
438
    {
439
    final float ratio = 1.0f/(NUM_FACES+1);
440
    final Static4D[] maps = new Static4D[NUM_FACES];
441

    
442
    try
443
      {
444
      maps[face] = new Static4D( newColor*ratio, 0.0f, ratio, 1.0f);
445
      mMesh.setTextureMap(maps,NUM_FACES*cubit);
446
      }
447
    catch(ArrayIndexOutOfBoundsException ignored)
448
      {
449
      // the object must have changed in between of us touching the screen and getting here
450
      // (which happens after the next render, i.e. about 30 miliseconds later)
451
      // ignore.
452
      }
453
    }
454

    
455
///////////////////////////////////////////////////////////////////////////////////////////////////
456

    
457
  public void beginNewRotation(int axis, int row )
458
    {
459
    if( axis<0 || axis>=ROTATION_AXIS.length )
460
      {
461
      android.util.Log.e("object", "invalid rotation axis: "+axis);
462
      return;
463
      }
464
    if( row<0 || row>=mSize )
465
      {
466
      android.util.Log.e("object", "invalid rotation row: "+row);
467
      return;
468
      }
469

    
470
    mRotAxis     = axis;
471
    mRotRowBitmap= (1<<row);
472
    mRotationAngleStatic.set0(0.0f);
473
    mRotationAxis.set( ROTATION_AXIS[axis] );
474
    mRotationAngle.add(mRotationAngleStatic);
475
    mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*RubikObjectList.MAX_OBJECT_SIZE) , -1);
476
    }
477

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

    
480
  public long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
481
    {
482
    mRotAxis     = axis;
483
    mRotRowBitmap= rowBitmap;
484

    
485
    mRotationAngleStatic.set0(0.0f);
486
    mRotationAxis.set( ROTATION_AXIS[axis] );
487
    mRotationAngle.setDuration(durationMillis);
488
    mRotationAngle.resetToBeginning();
489
    mRotationAngle.add(new Static1D(0));
490
    mRotationAngle.add(new Static1D(angle));
491
    mRotateEffect.setMeshAssociation( mRotRowBitmap<<(axis*RubikObjectList.MAX_OBJECT_SIZE) , -1);
492
    mRotateEffect.notifyWhenFinished(listener);
493

    
494
    return mRotateEffect.getID();
495
    }
496

    
497
///////////////////////////////////////////////////////////////////////////////////////////////////
498

    
499
  public long finishRotationNow(EffectListener listener)
500
    {
501
    float angle = getAngle();
502
    int nearestAngleInDegrees = computeNearestAngle(angle);
503
    mRotationAngleStatic.set0(angle);
504
    mRotationAngleFinal.set0(nearestAngleInDegrees);
505
    mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
506

    
507
    mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
508
    mRotationAngle.resetToBeginning();
509
    mRotationAngle.removeAll();
510
    mRotationAngle.add(mRotationAngleStatic);
511
    mRotationAngle.add(mRotationAngleMiddle);
512
    mRotationAngle.add(mRotationAngleFinal);
513
    mRotateEffect.notifyWhenFinished(listener);
514

    
515
    return mRotateEffect.getID();
516
    }
517

    
518

    
519
///////////////////////////////////////////////////////////////////////////////////////////////////
520

    
521
  private float getAngle()
522
    {
523
    int pointNum = mRotationAngle.getNumPoints();
524

    
525
    if( pointNum>=1 )
526
      {
527
      return mRotationAngle.getPoint(pointNum-1).get0();
528
      }
529
    else
530
      {
531
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
532
      crashlytics.log("points in RotationAngle: "+pointNum);
533
      return 0;
534
      }
535
    }
536

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

    
539
  public void removeRotationNow()
540
     {
541
     float angle = getAngle();
542
     int nearestAngleInDegrees = computeNearestAngle(angle);
543
     double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
544
     float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
545
     float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
546
     float axisX = ROTATION_AXIS[mRotAxis].get0();
547
     float axisY = ROTATION_AXIS[mRotAxis].get1();
548
     float axisZ = ROTATION_AXIS[mRotAxis].get2();
549
     Static4D quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
550

    
551
     mRotationAngle.removeAll();
552
     mRotationAngleStatic.set0(0);
553

    
554
     for(int i=0; i<NUM_CUBITS; i++)
555
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
556
         {
557
         mCubits[i].removeRotationNow(quat);
558
         }
559
     }
560

    
561
///////////////////////////////////////////////////////////////////////////////////////////////////
562

    
563
  public void initializeObject(int[][] moves)
564
    {
565
    solve();
566
    setupPosition(moves);
567
    }
568

    
569
///////////////////////////////////////////////////////////////////////////////////////////////////
570

    
571
  public int getCubit(float[] point3D)
572
    {
573
    float dist, minDist = Float. MAX_VALUE;
574
    int currentBest=-1;
575
    float multiplier = returnMultiplier();
576

    
577
    point3D[0] *= multiplier;
578
    point3D[1] *= multiplier;
579
    point3D[2] *= multiplier;
580

    
581
    for(int i=0; i<NUM_CUBITS; i++)
582
      {
583
      dist = mCubits[i].getDistSquared(point3D);
584
      if( dist<minDist )
585
        {
586
        minDist = dist;
587
        currentBest = i;
588
        }
589
      }
590

    
591
    return currentBest;
592
    }
593

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

    
596
  public int computeNearestAngle(float angle)
597
    {
598
    final int NEAREST = 360/getBasicAngle();
599

    
600
    int tmp = (int)((angle+NEAREST/2)/NEAREST);
601
    if( angle< -(NEAREST*0.5) ) tmp-=1;
602

    
603
    return NEAREST*tmp;
604
    }
605

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

    
608
  public RubikObjectList getObjectList()
609
    {
610
    return mList;
611
    }
612

    
613
///////////////////////////////////////////////////////////////////////////////////////////////////
614

    
615
  abstract float getScreenRatio();
616
  abstract Static3D[] getCubitPositions(int size);
617
  abstract float[] getLegalQuats();
618
  abstract int getNumFaces();
619
  abstract MeshBase createCubitMesh(int cubit);
620
  abstract void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top, int side);
621
  public abstract Static3D[] getRotationAxis();
622
  public abstract int getBasicAngle();
623
  public abstract float returnMultiplier();
624
  public abstract float returnRotationFactor(float offset);
625
  public abstract String retObjectString();
626
  public abstract float[] getRowChances();
627
  }
(4-4/8)