Project

General

Profile

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

magiccube / src / main / java / org / distorted / object / RubikObject.java @ 8becce57

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.object;
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.MatrixEffectQuaternion;
29
import org.distorted.library.effect.MatrixEffectScale;
30
import org.distorted.library.effect.VertexEffectSink;
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.MeshRectangles;
36
import org.distorted.library.message.EffectListener;
37
import org.distorted.library.type.Dynamic1D;
38
import org.distorted.library.type.Static1D;
39
import org.distorted.library.type.Static3D;
40
import org.distorted.library.type.Static4D;
41

    
42
import static org.distorted.magic.RubikRenderer.NODE_FBO_SIZE;
43

    
44
///////////////////////////////////////////////////////////////////////////////////////////////////
45

    
46
public abstract class RubikObject extends DistortedNode
47
  {
48
  static final int TEXTURE_HEIGHT = 128;
49
  final float[] LEGAL_QUATS;
50
  final Static3D[] ROTATION_AXIS;
51

    
52
  static float OBJECT_SCREEN_RATIO;
53

    
54
  private static final int POST_ROTATION_MILLISEC = 500;
55
  private final int NUM_CUBITS;
56
  private int mRotRowBitmap;
57
  private int mRotAxis;
58
  private Static3D[] mOrigPos;
59
  private Static3D mScale, mNodeScale;
60
  private Static4D mQuatAccumulated;
61
  private Cubit[] mCubits;
62
  private int mSize;
63
  private RubikObjectList mList;
64

    
65
  float mStart, mStep;
66

    
67
  Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
68
  DistortedTexture mTexture;
69

    
70
  VertexEffectSink mSinkEffect;
71
  MatrixEffectScale mScaleEffect;
72
  MatrixEffectQuaternion mQuatCEffect;
73
  MatrixEffectQuaternion mQuatAEffect;
74

    
75
///////////////////////////////////////////////////////////////////////////////////////////////////
76

    
77
  RubikObject(int size, int fov, Static4D quatCur, Static4D quatAcc, DistortedTexture nodeTexture,
78
              MeshRectangles nodeMesh, DistortedEffects nodeEffects, String moves, RubikObjectList list)
79
    {
80
    super(nodeTexture,nodeEffects,nodeMesh);
81

    
82
    resizeFBO(NODE_FBO_SIZE, NODE_FBO_SIZE);
83

    
84
    mList = list;
85
    mOrigPos = getCubitPositions(size);
86

    
87
    LEGAL_QUATS = getLegalQuats();
88
    NUM_CUBITS  = mOrigPos.length;
89
    ROTATION_AXIS = getRotationAxis();
90
    OBJECT_SCREEN_RATIO = getScreenRatio();
91

    
92
    mSize = size;
93
    computeStartAndStep(mOrigPos);
94

    
95
    mRotationAngleStatic = new Static1D(0);
96
    mRotationAngleMiddle = new Static1D(0);
97
    mRotationAngleFinal  = new Static1D(0);
98

    
99
    mScale    = new Static3D(1,1,1);
100
    mNodeScale= new Static3D(1,1,1);
101

    
102
    mQuatAccumulated = quatAcc;
103

    
104
    Static3D center = new Static3D(0,0,0);
105

    
106
    mSinkEffect  = getSink(mSize);
107
    mScaleEffect = new MatrixEffectScale(mScale);
108
    mQuatCEffect = new MatrixEffectQuaternion(quatCur, center);
109
    mQuatAEffect = new MatrixEffectQuaternion(quatAcc, center);
110

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

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

    
117
    int vertices = (int)(24.0f/mSize + 2.0f);
118

    
119
    for(int i=0; i<NUM_CUBITS; i++)
120
      {
121
      MeshBase cubitMesh = createCubitMesh(i,vertices);
122
      mCubits[i] = new Cubit(this,cubitMesh,mOrigPos[i]);
123
      textureCubitMesh(cubitMesh,i);
124

    
125
      attach(mCubits[i].mNode);
126
      }
127

    
128
    setupPosition(moves);
129

    
130
    setProjection(fov, 0.1f);
131
    }
132

    
133
///////////////////////////////////////////////////////////////////////////////////////////////////
134

    
135
  private void textureCubitMesh(MeshBase mesh, int cubit)
136
    {
137
    boolean belongs;
138
    final int numFaces = getNumFaces();
139
    final Static4D[] maps = new Static4D[numFaces];
140
    final float ratio = 1.0f/(numFaces+1);
141

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

    
159
    mesh.setTextureMap(maps);
160
    }
161

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

    
171
  private void computeStartAndStep(Static3D[] pos)
172
    {
173
    float min = Float.MAX_VALUE;
174
    float max = Float.MIN_VALUE;
175
    float axisX = ROTATION_AXIS[0].get0();
176
    float axisY = ROTATION_AXIS[0].get1();
177
    float axisZ = ROTATION_AXIS[0].get2();
178
    float tmp;
179

    
180
    for(int i=0; i<NUM_CUBITS; i++)
181
      {
182
      tmp = pos[i].get0()*axisX + pos[i].get1()*axisY + pos[i].get2()*axisZ;
183
      if( tmp<min ) min=tmp;
184
      if( tmp>max ) max=tmp;
185
      }
186

    
187
    mStart = min;
188
    mStep  = (max-min+1.0f)/mSize;
189
    }
190

    
191
///////////////////////////////////////////////////////////////////////////////////////////////////
192

    
193
  private boolean belongsToRotation( int cubit, int axis, int rowBitmap)
194
    {
195
    int cubitRow = (int)(mCubits[cubit].mRotationRow[axis]+0.5f);
196
    return ((1<<cubitRow)&rowBitmap)!=0;
197
    }
198

    
199
///////////////////////////////////////////////////////////////////////////////////////////////////
200
// we cannot use belongsToRotation for deciding if to texture a face. Counterexample: the 'rotated'
201
// tetrahedrons of Pyraminx nearby the edge: they belong to rotation but their face which is rotated
202
// away from the face of the Pyraminx shouldn't be textured.
203

    
204
  private boolean isOnFace( int cubit, int axis, int row)
205
    {
206
    final float MAX_ERROR = 0.0001f;
207
    float diff = mCubits[cubit].mRotationRow[axis] - row;
208
    return diff*diff < MAX_ERROR;
209
    }
210

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

    
213
  private void resetRotationAngle(Dynamic1D rotationAngle)
214
    {
215
    rotationAngle.setDuration(POST_ROTATION_MILLISEC);
216
    rotationAngle.resetToBeginning();
217
    rotationAngle.removeAll();
218
    rotationAngle.add(mRotationAngleStatic);
219
    rotationAngle.add(mRotationAngleMiddle);
220
    rotationAngle.add(mRotationAngleFinal);
221
    }
222

    
223
///////////////////////////////////////////////////////////////////////////////////////////////////
224
// TODO
225

    
226
  private void setupPosition(String moves)
227
    {
228
    android.util.Log.e("object", "initializing: "+moves);
229
/*
230
    int index,tmp, a1,a2,a3, rv, rc, ra;
231

    
232
		numRotations=0;
233
		initializeVertices(mSize);
234

    
235
		int len=moves.length()/4;
236

    
237
		for(int i=0; i<len; i++)
238
		  {
239
			a1=moves.charAt(4*i+1)-'0';
240
			a2=moves.charAt(4*i+2)-'0';
241
			a3=moves.charAt(4*i+3)-'0';
242

    
243
			rv = (10*a1+a2)/32;
244
			rc = (10*a1+a2)%32;
245
			ra = 2-a3;
246

    
247
			rotVector = rv;
248

    
249
			tmp=rc;
250
			index=0;
251

    
252
			while( tmp!=0 )
253
			  {
254
				if( (tmp&0x1)==1 )
255
				  {
256
					rotAngle[index] = ra*90;
257
					RubikWorld.setIdentity(rotMat[index]);
258

    
259
					switch(rotVector)
260
					  {
261
					  case VECTX: RubikWorld.postRotate( rotMat[index], (float)((ra*90)%360), 1f,0f,0f); break;
262
					  case VECTY: RubikWorld.postRotate( rotMat[index], (float)((ra*90)%360), 0f,1f,0f); break;
263
					  case VECTZ: RubikWorld.postRotate( rotMat[index], (float)((ra*90)%360), 0f,0f,1f); break;
264
					  }
265

    
266
					transposeTiles(index);
267
					rotAngle[index]=0;
268
				  }
269

    
270
				tmp/=2;
271
				index++;
272
			  }
273
	  	}
274
 */
275
    }
276

    
277
///////////////////////////////////////////////////////////////////////////////////////////////////
278
// Clamp all rotated positions to one of those original ones to avoid accumulating errors.
279

    
280
  void clampPos(Static3D pos)
281
    {
282
    float currError, minError = Float.MAX_VALUE;
283
    int minErrorIndex= -1;
284
    float x = pos.get0();
285
    float y = pos.get1();
286
    float z = pos.get2();
287
    float xo,yo,zo;
288

    
289
    for(int i=0; i<NUM_CUBITS; i++)
290
      {
291
      xo = mOrigPos[i].get0();
292
      yo = mOrigPos[i].get1();
293
      zo = mOrigPos[i].get2();
294

    
295
      currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
296

    
297
      if( currError<minError )
298
        {
299
        minError = currError;
300
        minErrorIndex = i;
301
        }
302
      }
303

    
304
    pos.set( mOrigPos[minErrorIndex] );
305
    }
306

    
307
///////////////////////////////////////////////////////////////////////////////////////////////////
308
// the getFaceColors + final black in a horizontal strip.
309

    
310
  public void createTexture()
311
    {
312
    Bitmap bitmap;
313

    
314
    final int numColors = getNumFaces();
315

    
316
    Paint paint = new Paint();
317
    bitmap = Bitmap.createBitmap( (numColors+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
318
    Canvas canvas = new Canvas(bitmap);
319

    
320
    paint.setAntiAlias(true);
321
    paint.setTextAlign(Paint.Align.CENTER);
322
    paint.setStyle(Paint.Style.FILL);
323

    
324
    paint.setColor(0xff000000);
325
    canvas.drawRect(0, 0, (numColors+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, paint);
326

    
327
    for(int i=0; i<numColors; i++)
328
      {
329
      createFaceTexture(canvas,paint,i);
330
      }
331

    
332
    mTexture.setTexture(bitmap);
333
    }
334

    
335
///////////////////////////////////////////////////////////////////////////////////////////////////
336

    
337
  public int getSize()
338
    {
339
    return mSize;
340
    }
341

    
342
///////////////////////////////////////////////////////////////////////////////////////////////////
343

    
344
  public void continueRotation(float angleInDegrees)
345
    {
346
    mRotationAngleStatic.set0(angleInDegrees);
347
    }
348

    
349
///////////////////////////////////////////////////////////////////////////////////////////////////
350

    
351
  public Static4D getRotationQuat()
352
      {
353
      return mQuatAccumulated;
354
      }
355

    
356
///////////////////////////////////////////////////////////////////////////////////////////////////
357

    
358
  public void recomputeScaleFactor(int scrWidth, int scrHeight)
359
    {
360
    float factor = Math.min(scrWidth,scrHeight);
361
    float scaleFactor = OBJECT_SCREEN_RATIO*NODE_FBO_SIZE/mSize;
362

    
363
    mNodeScale.set(factor,factor,factor);
364
    mScale.set(scaleFactor,scaleFactor,scaleFactor);
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 long finishRotationNow(EffectListener listener)
384
    {
385
    boolean first = true;
386
    long effectID=0;
387

    
388
    for(int i=0; i<NUM_CUBITS; i++)
389
      {
390
      if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
391
        {
392
        if( first )
393
          {
394
          first=false;
395
          effectID = mCubits[i].finishRotationNow(listener);
396
          }
397
        resetRotationAngle(mCubits[i].mRotationAngle);
398
        }
399
      }
400

    
401
    return effectID;
402
    }
403

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

    
406
  public void releaseResources()
407
    {
408
    mTexture.markForDeletion();
409

    
410
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].releaseResources();
411
    }
412

    
413
///////////////////////////////////////////////////////////////////////////////////////////////////
414

    
415
  public void apply(Effect effect, int position)
416
    {
417
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.apply(effect, position);
418
    }
419

    
420
///////////////////////////////////////////////////////////////////////////////////////////////////
421

    
422
  public void remove(long effectID)
423
    {
424
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.abortById(effectID);
425
    }
426

    
427
///////////////////////////////////////////////////////////////////////////////////////////////////
428

    
429
  public void solve()
430
    {
431
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].solve();
432
    }
433

    
434
///////////////////////////////////////////////////////////////////////////////////////////////////
435

    
436
  public boolean isSolved()
437
    {
438
    Static4D q = mCubits[0].mQuatScramble;
439

    
440
    for(int i=1; i<NUM_CUBITS; i++)
441
      {
442
      if( !mCubits[i].thereIsNoVisibleDifference(q) ) return false;
443
      }
444

    
445
    return true;
446
    }
447

    
448
///////////////////////////////////////////////////////////////////////////////////////////////////
449

    
450
  public void beginNewRotation(int axis, int row )
451
    {
452
    if( axis<0 || axis>=ROTATION_AXIS.length )
453
      {
454
      android.util.Log.e("object", "invalid rotation axis: "+axis);
455
      return;
456
      }
457
    if( row<0 || row>=mSize )
458
      {
459
      android.util.Log.e("object", "invalid rotation row: "+row);
460
      return;
461
      }
462

    
463
    mRotAxis       = axis;
464
    mRotRowBitmap  = (1<<row);
465

    
466
    mRotationAngleStatic.set0(0.0f);
467

    
468
    for(int i=0; i<NUM_CUBITS; i++)
469
      if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
470
        {
471
        mCubits[i].beginNewRotation(axis);
472
        }
473
     }
474

    
475
///////////////////////////////////////////////////////////////////////////////////////////////////
476

    
477
  public long addNewRotation( int axis, int rowBitmap, int angle, long durationMillis, EffectListener listener )
478
     {
479
     long effectID=0;
480
     boolean first = true;
481

    
482
     mRotAxis       = axis;
483
     mRotRowBitmap  = rowBitmap;
484

    
485
     mRotationAngleStatic.set0(0.0f);
486

    
487
     for(int i=0; i<NUM_CUBITS; i++)
488
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
489
         {
490
         mCubits[i].addNewRotation(axis,durationMillis,angle);
491

    
492
         if( first )
493
           {
494
           first = false;
495
           effectID = mCubits[i].setUpCallback(listener);
496
           }
497
         }
498

    
499
     return effectID;
500
     }
501

    
502
///////////////////////////////////////////////////////////////////////////////////////////////////
503

    
504
  public void removeRotationNow()
505
     {
506
     boolean first = true;
507
     Static4D quat = null;
508

    
509
     for(int i=0; i<NUM_CUBITS; i++)
510
       if( belongsToRotation(i,mRotAxis,mRotRowBitmap) )
511
         {
512
         if( first )
513
           {
514
           first = false;
515
           quat = mCubits[i].returnRotationQuat(mRotAxis);
516
           }
517

    
518
         mCubits[i].removeRotationNow(quat);
519
         }
520

    
521
     mRotationAngleStatic.set0(0);
522
     }
523

    
524
///////////////////////////////////////////////////////////////////////////////////////////////////
525

    
526
  public void initializeObject(String moves)
527
    {
528
    solve();
529
    setupPosition(moves);
530
    }
531

    
532
///////////////////////////////////////////////////////////////////////////////////////////////////
533

    
534
  public RubikObjectList getObjectList()
535
    {
536
    return mList;
537
    }
538

    
539
///////////////////////////////////////////////////////////////////////////////////////////////////
540

    
541
  abstract float getScreenRatio();
542
  abstract VertexEffectSink getSink(int size);
543
  abstract Static3D[] getCubitPositions(int size);
544
  abstract float[] getLegalQuats();
545
  abstract int getNumFaces();
546
  abstract void createFaceTexture(Canvas canvas, Paint paint, int face);
547
  abstract MeshBase createCubitMesh(int cubit, int vertices);
548
  public abstract Static3D[] getRotationAxis();
549
  public abstract int getBasicAngle();
550
  public abstract int returnRowFromOffset(float offset);
551
  public abstract float returnRotationFactor(float offset);
552
  }
(4-4/8)