Project

General

Profile

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

magiccube / src / main / java / org / distorted / objects / RubikObject.java @ 470820a7

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 org.distorted.library.effect.Effect;
28
import org.distorted.library.effect.MatrixEffectMove;
29
import org.distorted.library.effect.MatrixEffectQuaternion;
30
import org.distorted.library.effect.MatrixEffectScale;
31
import org.distorted.library.main.DistortedEffects;
32
import org.distorted.library.main.DistortedNode;
33
import org.distorted.library.main.DistortedTexture;
34
import org.distorted.library.mesh.MeshBase;
35
import org.distorted.library.mesh.MeshJoined;
36
import org.distorted.library.mesh.MeshRectangles;
37
import org.distorted.library.message.EffectListener;
38
import org.distorted.library.type.Static1D;
39
import org.distorted.library.type.Static3D;
40
import org.distorted.library.type.Static4D;
41

    
42
///////////////////////////////////////////////////////////////////////////////////////////////////
43

    
44
public abstract class RubikObject extends DistortedNode
45
  {
46
  static final int INTERIOR_COLOR = 0xff000000;
47
  public static final int NODE_FBO_SIZE = 600;
48

    
49
  private static final int TEXTURE_HEIGHT = 128;
50
  final float[] LEGAL_QUATS;
51
  final Static3D[] ROTATION_AXIS;
52
  final int NUM_FACES;
53

    
54
  static float OBJECT_SCREEN_RATIO;
55

    
56
  private final int NUM_CUBITS;
57
  private int mRotRowBitmap;
58
  private int mRotAxis;
59
  private Static3D[] mOrigPos;
60
  private Static3D mNodeScale;
61
  private Static4D mQuatAccumulated;
62
  private Cubit[] mCubits;
63
  private int mSize;
64
  private RubikObjectList mList;
65
  private MeshBase mMesh;
66
  private DistortedEffects mEffects;
67

    
68
  float mStart, mStep;
69

    
70
  Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
71
  DistortedTexture mTexture;
72

    
73
  MatrixEffectScale mScaleEffect;
74
  MatrixEffectQuaternion mQuatCEffect;
75
  MatrixEffectQuaternion mQuatAEffect;
76

    
77
///////////////////////////////////////////////////////////////////////////////////////////////////
78

    
79
  RubikObject(int size, int fov, Static4D quatCur, Static4D quatAcc, DistortedTexture nodeTexture,
80
              MeshRectangles nodeMesh, DistortedEffects nodeEffects, int[][] moves, RubikObjectList list)
81
    {
82
    super(nodeTexture,nodeEffects,nodeMesh);
83

    
84
    resizeFBO(NODE_FBO_SIZE, NODE_FBO_SIZE);
85

    
86
    mList = list;
87
    mOrigPos = getCubitPositions(size);
88

    
89
    LEGAL_QUATS = getLegalQuats();
90
    NUM_CUBITS  = mOrigPos.length;
91
    ROTATION_AXIS = getRotationAxis();
92
    OBJECT_SCREEN_RATIO = getScreenRatio();
93
    NUM_FACES = getNumFaces();
94

    
95
    mSize = size;
96
    computeStartAndStep(mOrigPos);
97
    mNodeScale= new Static3D(1,1,1);
98
    mQuatAccumulated = quatAcc;
99

    
100
    mRotationAngleStatic = new Static1D(0);
101
    mRotationAngleMiddle = new Static1D(0);
102
    mRotationAngleFinal  = new Static1D(0);
103

    
104
    Static3D center = new Static3D(0,0,0);
105
    float scale = OBJECT_SCREEN_RATIO*NODE_FBO_SIZE/mSize;
106
    mScaleEffect = new MatrixEffectScale(new Static3D(scale,scale,scale));
107
    mQuatCEffect = new MatrixEffectQuaternion(quatCur, center);
108
    mQuatAEffect = new MatrixEffectQuaternion(quatAcc, center);
109

    
110
    MatrixEffectScale nodeScaleEffect = new MatrixEffectScale(mNodeScale);
111
    nodeEffects.apply(nodeScaleEffect);
112

    
113
    mCubits = new Cubit[NUM_CUBITS];
114
    mTexture = new DistortedTexture();
115
    MeshBase[] cubitMesh = new MeshBase[NUM_CUBITS];
116

    
117
    for(int i=0; i<NUM_CUBITS; i++)
118
      {
119
      cubitMesh[i] = createCubitMesh(i);
120
      cubitMesh[i].apply(new MatrixEffectMove(mOrigPos[i]),1,0);
121
      mCubits[i] = new Cubit(this,mOrigPos[i]);
122
      }
123

    
124
    mMesh = new MeshJoined(cubitMesh);
125

    
126
    resetAllTextureMaps();
127

    
128
    mEffects = new DistortedEffects();
129
    mEffects.apply(mQuatAEffect);
130
    mEffects.apply(mQuatCEffect);
131
    mEffects.apply(mScaleEffect);
132

    
133
    attach( new DistortedNode(mTexture,mEffects,mMesh) );
134

    
135
    setupPosition(moves);
136

    
137
    setProjection(fov, 0.1f);
138
    }
139

    
140
///////////////////////////////////////////////////////////////////////////////////////////////////
141

    
142
  private void textureCubitMesh(MeshBase mesh, int cubit)
143
    {
144
    boolean belongs;
145
    final Static4D[] maps = new Static4D[NUM_FACES];
146
    final float ratio = 1.0f/(NUM_FACES+1);
147

    
148
    if( 2*ROTATION_AXIS.length == NUM_FACES )  // i.e. there are faces on both ends of the axis (cube)
149
      {
150
      for(int i=0; i<NUM_FACES; i++)
151
        {
152
        belongs = isOnFace(cubit, i/2, i%2==0 ? mSize-1:0 );
153
        maps[i] = new Static4D( (belongs?i:NUM_FACES)*ratio, 0.0f, ratio, 1.0f);
154
        }
155
      }
156
    else if( ROTATION_AXIS.length == NUM_FACES )  // just a single face on the right end of an axis (pyraminx)
157
      {
158
      for(int i=0; i<NUM_FACES; i++)
159
        {
160
        belongs = isOnFace(cubit, i, 0 );
161
        maps[i] = new Static4D( (belongs?i:NUM_FACES)*ratio, 0.0f, ratio, 1.0f);
162
        }
163
      }
164

    
165
    mesh.setTextureMap(maps,NUM_FACES*cubit);
166
    }
167

    
168
///////////////////////////////////////////////////////////////////////////////////////////////////
169
// Cast centers of all Cubits on the first rotation Axis and compute the leftmost and rightmost
170
// one. From there compute the 'start' (i.e. the leftmost) and 'step' (i.e. distance between two
171
// consecutive).
172
// it is assumed that other rotation axis have the same 'start' and 'step' - this is the case with
173
// the Cube and the Pyraminx.
174
// Start and Step are then needed to compute which rotation row (with respect to a given axis) a
175
// given Cubit belongs to.
176

    
177
  private void computeStartAndStep(Static3D[] pos)
178
    {
179
    float min = Float.MAX_VALUE;
180
    float max = Float.MIN_VALUE;
181
    float axisX = ROTATION_AXIS[0].get0();
182
    float axisY = ROTATION_AXIS[0].get1();
183
    float axisZ = ROTATION_AXIS[0].get2();
184
    float tmp;
185

    
186
    for(int i=0; i<NUM_CUBITS; i++)
187
      {
188
      tmp = pos[i].get0()*axisX + pos[i].get1()*axisY + pos[i].get2()*axisZ;
189
      if( tmp<min ) min=tmp;
190
      if( tmp>max ) max=tmp;
191
      }
192

    
193
    mStart = min;
194
    mStep  = (max-min+1.0f)/mSize;
195
    }
196

    
197
///////////////////////////////////////////////////////////////////////////////////////////////////
198

    
199
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
200
    {
201
    int cubitRow = (int)(mCubits[cubit].mRotationRow[axis]+0.5f);
202
    return ((1<<cubitRow)&rowBitmap)!=0;
203
    }
204

    
205
///////////////////////////////////////////////////////////////////////////////////////////////////
206
// we cannot use belongsToRotation for deciding if to texture a face. Counterexample: the 'rotated'
207
// tetrahedrons of Pyraminx nearby the edge: they belong to rotation but their face which is rotated
208
// away from the face of the Pyraminx shouldn't be textured.
209

    
210
  private boolean isOnFace( int cubit, int axis, int row)
211
    {
212
    final float MAX_ERROR = 0.0001f;
213
    float diff = mCubits[cubit].mRotationRow[axis] - row;
214
    return diff*diff < MAX_ERROR;
215
    }
216

    
217
///////////////////////////////////////////////////////////////////////////////////////////////////
218
// note the minus in front of the sin() - we rotate counterclockwise
219
// when looking towards the direction where the axis increases in values.
220

    
221
  private Static4D makeQuaternion(int axisIndex, int angleInDegrees)
222
    {
223
    Static3D axis = ROTATION_AXIS[axisIndex];
224

    
225
    while( angleInDegrees<0 ) angleInDegrees += 360;
226
    angleInDegrees %= 360;
227
    
228
    float cosA = (float)Math.cos(Math.PI*angleInDegrees/360);
229
    float sinA =-(float)Math.sqrt(1-cosA*cosA);
230

    
231
    return new Static4D(axis.get0()*sinA, axis.get1()*sinA, axis.get2()*sinA, cosA);
232
    }
233

    
234
///////////////////////////////////////////////////////////////////////////////////////////////////
235

    
236
  private void setupPosition(int[][] moves)
237
    {
238
    if( moves!=null )
239
      {
240
      Static4D quat;
241
      int axis, rowBitmap, angle;
242
      int corr = (360/getBasicAngle());
243

    
244
      for(int[] move: moves)
245
        {
246
        axis     = move[0];
247
        rowBitmap= move[1];
248
        angle    = move[2]*corr;
249
        quat     = makeQuaternion(axis,angle);
250

    
251
        for(int j=0; j<NUM_CUBITS; j++)
252
          if( belongsToRotation(j,axis,rowBitmap) )
253
            {
254
            mCubits[j].removeRotationNow(quat);
255
            }
256
        }
257
      }
258
    }
259

    
260
///////////////////////////////////////////////////////////////////////////////////////////////////
261

    
262
  int getCubitFaceColorIndex(int cubit, int face)
263
    {
264
    Static4D texMap = mMesh.getTextureMap(NUM_FACES*cubit + face);
265
    return (int)(texMap.get0() / texMap.get2());
266
    }
267

    
268
///////////////////////////////////////////////////////////////////////////////////////////////////
269
// Clamp all rotated positions to one of those original ones to avoid accumulating errors.
270

    
271
  void clampPos(Static3D pos)
272
    {
273
    float currError, minError = Float.MAX_VALUE;
274
    int minErrorIndex= -1;
275
    float x = pos.get0();
276
    float y = pos.get1();
277
    float z = pos.get2();
278
    float xo,yo,zo;
279

    
280
    for(int i=0; i<NUM_CUBITS; i++)
281
      {
282
      xo = mOrigPos[i].get0();
283
      yo = mOrigPos[i].get1();
284
      zo = mOrigPos[i].get2();
285

    
286
      currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
287

    
288
      if( currError<minError )
289
        {
290
        minError = currError;
291
        minErrorIndex = i;
292
        }
293
      }
294

    
295
    pos.set( mOrigPos[minErrorIndex] );
296
    }
297

    
298
///////////////////////////////////////////////////////////////////////////////////////////////////
299
// the getFaceColors + final black in a horizontal strip.
300

    
301
  public void createTexture()
302
    {
303
    Bitmap bitmap;
304

    
305
    Paint paint = new Paint();
306
    bitmap = Bitmap.createBitmap( (NUM_FACES+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
307
    Canvas canvas = new Canvas(bitmap);
308

    
309
    paint.setAntiAlias(true);
310
    paint.setTextAlign(Paint.Align.CENTER);
311
    paint.setStyle(Paint.Style.FILL);
312

    
313
    paint.setColor(INTERIOR_COLOR);
314
    canvas.drawRect(0, 0, (NUM_FACES+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, paint);
315

    
316
    for(int i=0; i<NUM_FACES; i++)
317
      {
318
      createFaceTexture(canvas, paint, i, i*TEXTURE_HEIGHT, 0, TEXTURE_HEIGHT);
319
      }
320

    
321
    mTexture.setTexture(bitmap);
322
    }
323

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

    
326
  public int getSize()
327
    {
328
    return mSize;
329
    }
330

    
331
///////////////////////////////////////////////////////////////////////////////////////////////////
332

    
333
  public void continueRotation(float angleInDegrees)
334
    {
335
    mRotationAngleStatic.set0(angleInDegrees);
336
    }
337

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

    
340
  public Static4D getRotationQuat()
341
      {
342
      return mQuatAccumulated;
343
      }
344

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

    
347
  public void recomputeScaleFactor(int scrWidth, int scrHeight)
348
    {
349
    float factor = Math.min(scrWidth,scrHeight);
350
    mNodeScale.set(factor,factor,factor);
351
    }
352

    
353
///////////////////////////////////////////////////////////////////////////////////////////////////
354

    
355
  public void savePreferences(SharedPreferences.Editor editor)
356
    {
357
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].savePreferences(editor);
358
    }
359

    
360
///////////////////////////////////////////////////////////////////////////////////////////////////
361

    
362
  public void restorePreferences(SharedPreferences preferences)
363
    {
364
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].restorePreferences(preferences);
365
    }
366

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

    
369
  public void releaseResources()
370
    {
371
    mTexture.markForDeletion();
372

    
373
    // TODO ?
374
    }
375

    
376
///////////////////////////////////////////////////////////////////////////////////////////////////
377

    
378
  public void apply(Effect effect, int position)
379
    {
380
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.apply(effect, position);
381
    }
382

    
383
///////////////////////////////////////////////////////////////////////////////////////////////////
384

    
385
  public void remove(long effectID)
386
    {
387
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.abortById(effectID);
388
    }
389

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

    
392
  public void solve()
393
    {
394
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].solve();
395
    }
396

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

    
399
  public boolean isSolved()
400
    {
401
    Static4D q = mCubits[0].mQuatScramble;
402

    
403
    for(int i=1; i<NUM_CUBITS; i++)
404
      {
405
      if( !mCubits[i].thereIsNoVisibleDifference(q) ) return false;
406
      }
407

    
408
    return true;
409
    }
410

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

    
413
  public void resetAllTextureMaps()
414
    {
415
    for(int i=0; i<NUM_CUBITS; i++)
416
      {
417
      textureCubitMesh( mMesh , i );
418
      }
419
    }
420

    
421
///////////////////////////////////////////////////////////////////////////////////////////////////
422

    
423
  public void setTextureMap(int cubit, int face, int newColor)
424
    {
425
    final float ratio = 1.0f/(NUM_FACES+1);
426
    final Static4D[] maps = new Static4D[NUM_FACES];
427

    
428
    try
429
      {
430
      maps[face] = new Static4D( newColor*ratio, 0.0f, ratio, 1.0f);
431
      mMesh.setTextureMap(maps,NUM_FACES*cubit);
432
      }
433
    catch(ArrayIndexOutOfBoundsException ignored)
434
      {
435
      // the object must have changed in between of us touching the screen and getting here
436
      // (which happens after the next render, i.e. about 30 miliseconds later)
437
      // ignore.
438
      }
439
    }
440

    
441
///////////////////////////////////////////////////////////////////////////////////////////////////
442

    
443
  public void beginNewRotation(int axis, int row )
444
    {
445
    if( axis<0 || axis>=ROTATION_AXIS.length )
446
      {
447
      android.util.Log.e("object", "invalid rotation axis: "+axis);
448
      return;
449
      }
450
    if( row<0 || row>=mSize )
451
      {
452
      android.util.Log.e("object", "invalid rotation row: "+row);
453
      return;
454
      }
455

    
456
    mRotAxis       = axis;
457
    mRotRowBitmap  = (1<<row);
458

    
459
    mRotationAngleStatic.set0(0.0f);
460

    
461
    for(int i=0; i<NUM_CUBITS; i++)
462
      if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
463
        {
464
        mCubits[i].beginNewRotation(axis);
465
        }
466
     }
467

    
468
///////////////////////////////////////////////////////////////////////////////////////////////////
469

    
470
  public long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
471
     {
472
     long effectID=0;
473
     int firstCubit = -1;
474

    
475
     mRotAxis       = axis;
476
     mRotRowBitmap  = rowBitmap;
477

    
478
     mRotationAngleStatic.set0(0.0f);
479

    
480
     for(int i=0; i<NUM_CUBITS; i++)
481
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
482
         {
483
         mCubits[i].addNewRotation(axis,durationMillis,angle);
484
         if( firstCubit<0 ) firstCubit = i;
485
         }
486

    
487
     if( firstCubit>=0 ) effectID = mCubits[firstCubit].setUpCallback(listener);
488

    
489
     return effectID;
490
     }
491

    
492
///////////////////////////////////////////////////////////////////////////////////////////////////
493

    
494
  public long finishRotationNow(EffectListener listener)
495
    {
496
    int firstCubit= -1;
497
    long effectID =  0;
498

    
499
    for(int i=0; i<NUM_CUBITS; i++)
500
      {
501
      if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
502
        {
503
        if( firstCubit<0 )
504
          {
505
          firstCubit=i;
506

    
507
          float angle = mCubits[i].getAngle();
508
          int nearestAngleInDegrees = computeNearestAngle(angle);
509
          mRotationAngleStatic.set0(angle);
510
          mRotationAngleFinal.set0(nearestAngleInDegrees);
511
          mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-angle)*0.2f );
512
          }
513
        mCubits[i].resetRotationAngle();
514
        }
515
      }
516

    
517
    if( firstCubit>=0 ) effectID = mCubits[firstCubit].setUpCallback(listener);
518

    
519
    return effectID;
520
    }
521

    
522
///////////////////////////////////////////////////////////////////////////////////////////////////
523

    
524
  public void removeRotationNow()
525
     {
526
     boolean first = true;
527
     Static4D quat = null;
528

    
529
     for(int i=0; i<NUM_CUBITS; i++)
530
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
531
         {
532
         if( first )
533
           {
534
           first = false;
535

    
536
           float angle = mCubits[i].getAngle();
537
           int nearestAngleInDegrees = computeNearestAngle(angle);
538
           double nearestAngleInRadians = nearestAngleInDegrees*Math.PI/180;
539
           float sinA =-(float)Math.sin(nearestAngleInRadians*0.5);
540
           float cosA = (float)Math.cos(nearestAngleInRadians*0.5);
541
           float axisX = ROTATION_AXIS[mRotAxis].get0();
542
           float axisY = ROTATION_AXIS[mRotAxis].get1();
543
           float axisZ = ROTATION_AXIS[mRotAxis].get2();
544
           quat = new Static4D( axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
545
           }
546

    
547
         mCubits[i].removeRotationNow(quat);
548
         }
549

    
550
     mRotationAngleStatic.set0(0);
551
     }
552

    
553
///////////////////////////////////////////////////////////////////////////////////////////////////
554

    
555
  public void initializeObject(int[][] moves)
556
    {
557
    solve();
558
    setupPosition(moves);
559
    }
560

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

    
563
  public int getCubit(float[] point3D)
564
    {
565
    float dist, minDist = Float. MAX_VALUE;
566
    int currentBest=-1;
567
    float multiplier = returnMultiplier();
568

    
569
    point3D[0] *= multiplier;
570
    point3D[1] *= multiplier;
571
    point3D[2] *= multiplier;
572

    
573
    for(int i=0; i<NUM_CUBITS; i++)
574
      {
575
      dist = mCubits[i].getDistSquared(point3D);
576
      if( dist<minDist )
577
        {
578
        minDist = dist;
579
        currentBest = i;
580
        }
581
      }
582

    
583
    return currentBest;
584
    }
585

    
586
///////////////////////////////////////////////////////////////////////////////////////////////////
587

    
588
  public int computeNearestAngle(float angle)
589
    {
590
    final int NEAREST = 360/getBasicAngle();
591

    
592
    int tmp = (int)((angle+NEAREST/2)/NEAREST);
593
    if( angle< -(NEAREST*0.5) ) tmp-=1;
594

    
595
    return NEAREST*tmp;
596
    }
597

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

    
600
  public RubikObjectList getObjectList()
601
    {
602
    return mList;
603
    }
604

    
605
///////////////////////////////////////////////////////////////////////////////////////////////////
606

    
607
  abstract float getScreenRatio();
608
  abstract Static3D[] getCubitPositions(int size);
609
  abstract float[] getLegalQuats();
610
  abstract int getNumFaces();
611
  abstract MeshBase createCubitMesh(int cubit);
612
  abstract void createFaceTexture(Canvas canvas, Paint paint, int face, int left, int top, int side);
613
  public abstract Static3D[] getRotationAxis();
614
  public abstract int getBasicAngle();
615
  public abstract float returnMultiplier();
616
  public abstract float returnRotationFactor(float offset);
617
  public abstract String retObjectString();
618
  public abstract float[] getRowChances();
619
  }
(4-4/8)