Project

General

Profile

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

magiccube / src / main / java / org / distorted / object / RubikObject.java @ 7381193e

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 mRotRow;
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

    
64
  float mStart, mStep;
65

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

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

    
74
///////////////////////////////////////////////////////////////////////////////////////////////////
75

    
76
  RubikObject(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture nodeTexture, MeshRectangles nodeMesh, DistortedEffects nodeEffects)
77
    {
78
    super(nodeTexture,nodeEffects,nodeMesh);
79

    
80
    resizeFBO(NODE_FBO_SIZE, NODE_FBO_SIZE);
81

    
82
    mOrigPos = getCubitPositions(size);
83

    
84
    LEGAL_QUATS = getLegalQuats();
85
    NUM_CUBITS  = mOrigPos.length;
86
    ROTATION_AXIS = getRotationAxis();
87
    OBJECT_SCREEN_RATIO = getScreenRatio();
88

    
89
    mSize = size;
90
    computeStartAndStep(mOrigPos);
91

    
92
    mRotationAngleStatic = new Static1D(0);
93
    mRotationAngleMiddle = new Static1D(0);
94
    mRotationAngleFinal  = new Static1D(0);
95

    
96
    mScale    = new Static3D(1,1,1);
97
    mNodeScale= new Static3D(1,1,1);
98

    
99
    mQuatAccumulated = quatAcc;
100

    
101
    Static3D center = new Static3D(0,0,0);
102

    
103
    mSinkEffect  = getSink(mSize);
104
    mScaleEffect = new MatrixEffectScale(mScale);
105
    mQuatCEffect = new MatrixEffectQuaternion(quatCur, center);
106
    mQuatAEffect = new MatrixEffectQuaternion(quatAcc, center);
107

    
108
    MatrixEffectScale nodeScaleEffect = new MatrixEffectScale(mNodeScale);
109
    nodeEffects.apply(nodeScaleEffect);
110

    
111
    mCubits = new Cubit[NUM_CUBITS];
112
    mTexture = new DistortedTexture();
113

    
114
    int vertices = (int)(24.0f/mSize + 2.0f);
115

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

    
122
      attach(mCubits[i].mNode);
123
      }
124

    
125
    setProjection(30, 0.1f);
126
    }
127

    
128
///////////////////////////////////////////////////////////////////////////////////////////////////
129

    
130
  private void textureCubitMesh(MeshBase mesh, int cubit)
131
    {
132
    boolean belongs;
133
    final int numFaces = getNumFaces();
134
    final Static4D[] maps = new Static4D[numFaces];
135
    final float ratio = 1.0f/(numFaces+1);
136

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

    
154
    mesh.setTextureMap(maps);
155
    }
156

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

    
166
  private void computeStartAndStep(Static3D[] pos)
167
    {
168
    float min = Float.MAX_VALUE;
169
    float max = Float.MIN_VALUE;
170
    float axisX = ROTATION_AXIS[0].get0();
171
    float axisY = ROTATION_AXIS[0].get1();
172
    float axisZ = ROTATION_AXIS[0].get2();
173
    float tmp;
174

    
175
    for(int i=0; i<NUM_CUBITS; i++)
176
      {
177
      tmp = pos[i].get0()*axisX + pos[i].get1()*axisY + pos[i].get2()*axisZ;
178
      if( tmp<min ) min=tmp;
179
      if( tmp>max ) max=tmp;
180
      }
181

    
182
    mStart = min;
183
    mStep  = (max-min+1.0f)/mSize;
184
    }
185

    
186
///////////////////////////////////////////////////////////////////////////////////////////////////
187

    
188
  private boolean belongsToRotation( int cubit, int axis, int row)
189
    {
190
    return ((int)(mCubits[cubit].mRotationRow[axis]+0.5f))==row;
191
    }
192

    
193
///////////////////////////////////////////////////////////////////////////////////////////////////
194
// we cannot use belongsToRotation for deciding if to texture a face. Counterexample: the 'rotated'
195
// tetrahedrons of Pyraminx nearby the edge: they belong to rotation but their face which is rotated
196
// away from the face of the Pyraminx shouldn't be textured.
197

    
198
  private boolean isOnFace( int cubit, int axis, int row)
199
    {
200
    final float MAX_ERROR = 0.0001f;
201
    float diff = mCubits[cubit].mRotationRow[axis] - row;
202
    return diff*diff < MAX_ERROR;
203
    }
204

    
205
///////////////////////////////////////////////////////////////////////////////////////////////////
206

    
207
  private void resetRotationAngle(Dynamic1D rotationAngle)
208
    {
209
    rotationAngle.setDuration(POST_ROTATION_MILLISEC);
210
    rotationAngle.resetToBeginning();
211
    rotationAngle.removeAll();
212
    rotationAngle.add(mRotationAngleStatic);
213
    rotationAngle.add(mRotationAngleMiddle);
214
    rotationAngle.add(mRotationAngleFinal);
215
    }
216

    
217
///////////////////////////////////////////////////////////////////////////////////////////////////
218
// Clamp all rotated positions to one of those original ones to avoid accumulating errors.
219

    
220
  void clampPos(Static3D pos)
221
    {
222
    float currError, minError = Float.MAX_VALUE;
223
    int minErrorIndex= -1;
224
    float x = pos.get0();
225
    float y = pos.get1();
226
    float z = pos.get2();
227
    float xo,yo,zo;
228

    
229
    for(int i=0; i<NUM_CUBITS; i++)
230
      {
231
      xo = mOrigPos[i].get0();
232
      yo = mOrigPos[i].get1();
233
      zo = mOrigPos[i].get2();
234

    
235
      currError = (xo-x)*(xo-x) + (yo-y)*(yo-y) + (zo-z)*(zo-z);
236

    
237
      if( currError<minError )
238
        {
239
        minError = currError;
240
        minErrorIndex = i;
241
        }
242
      }
243

    
244
    pos.set( mOrigPos[minErrorIndex] );
245
    }
246

    
247
///////////////////////////////////////////////////////////////////////////////////////////////////
248
// the getFaceColors + final black in a horizontal strip.
249

    
250
  public void createTexture()
251
    {
252
    Bitmap bitmap;
253

    
254
    final int numColors = getNumFaces();
255

    
256
    Paint paint = new Paint();
257
    bitmap = Bitmap.createBitmap( (numColors+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888);
258
    Canvas canvas = new Canvas(bitmap);
259

    
260
    paint.setAntiAlias(true);
261
    paint.setTextAlign(Paint.Align.CENTER);
262
    paint.setStyle(Paint.Style.FILL);
263

    
264
    paint.setColor(0xff000000);
265
    canvas.drawRect(0, 0, (numColors+1)*TEXTURE_HEIGHT, TEXTURE_HEIGHT, paint);
266

    
267
    for(int i=0; i<numColors; i++)
268
      {
269
      createFaceTexture(canvas,paint,i);
270
      }
271

    
272
    mTexture.setTexture(bitmap);
273
    }
274

    
275
///////////////////////////////////////////////////////////////////////////////////////////////////
276

    
277
  public int getSize()
278
    {
279
    return mSize;
280
    }
281

    
282
///////////////////////////////////////////////////////////////////////////////////////////////////
283

    
284
  public void continueRotation(float angleInDegrees)
285
    {
286
    mRotationAngleStatic.set0(angleInDegrees);
287
    }
288

    
289
///////////////////////////////////////////////////////////////////////////////////////////////////
290

    
291
  public Static4D getRotationQuat()
292
      {
293
      return mQuatAccumulated;
294
      }
295

    
296
///////////////////////////////////////////////////////////////////////////////////////////////////
297

    
298
  public void recomputeScaleFactor(int scrWidth, int scrHeight)
299
    {
300
    float factor = Math.min(scrWidth,scrHeight);
301
    float scaleFactor = OBJECT_SCREEN_RATIO*NODE_FBO_SIZE/mSize;
302

    
303
    mNodeScale.set(factor,factor,factor);
304
    mScale.set(scaleFactor,scaleFactor,scaleFactor);
305
    }
306

    
307
///////////////////////////////////////////////////////////////////////////////////////////////////
308

    
309
  public void savePreferences(SharedPreferences.Editor editor)
310
    {
311
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].savePreferences(editor);
312
    }
313

    
314
///////////////////////////////////////////////////////////////////////////////////////////////////
315

    
316
  public void restorePreferences(SharedPreferences preferences)
317
    {
318
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].restorePreferences(preferences);
319
    }
320

    
321
///////////////////////////////////////////////////////////////////////////////////////////////////
322

    
323
  public long finishRotationNow(EffectListener listener)
324
    {
325
    boolean first = true;
326
    long effectID=0;
327

    
328
    for(int i=0; i<NUM_CUBITS; i++)
329
      {
330
      if( belongsToRotation(i,mRotAxis,mRotRow) )
331
        {
332
        if( first )
333
          {
334
          first=false;
335
          effectID = mCubits[i].finishRotationNow(listener);
336
          }
337
        resetRotationAngle(mCubits[i].mRotationAngle);
338
        }
339
      }
340

    
341
    return effectID;
342
    }
343

    
344
///////////////////////////////////////////////////////////////////////////////////////////////////
345

    
346
  public void releaseResources()
347
    {
348
    mTexture.markForDeletion();
349

    
350
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].releaseResources();
351
    }
352

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

    
355
  public void apply(Effect effect, int position)
356
    {
357
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.apply(effect, position);
358
    }
359

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

    
362
  public void remove(long effectID)
363
    {
364
    for(int i=0; i<NUM_CUBITS; i++) mCubits[i].mEffect.abortById(effectID);
365
    }
366

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

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

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

    
376
  public boolean isSolved()
377
    {
378
    Static4D q = mCubits[0].mQuatScramble;
379

    
380
    float x = q.get0();
381
    float y = q.get1();
382
    float z = q.get2();
383
    float w = q.get3();
384

    
385
    for(int i=0; i<NUM_CUBITS; i++)
386
      {
387
      q = mCubits[i].mQuatScramble;
388

    
389
      if( q.get0()!=x || q.get1()!=y || q.get2()!=z || q.get3()!=w ) return false;
390
      }
391

    
392
    return true;
393
    }
394

    
395
///////////////////////////////////////////////////////////////////////////////////////////////////
396

    
397
  public void beginNewRotation(int axis, int row )
398
    {
399
    if( axis<0 || axis>=ROTATION_AXIS.length )
400
      {
401
      android.util.Log.e("object", "invalid rotation axis: "+axis);
402
      return;
403
      }
404
    if( row<0 || row>=mSize )
405
      {
406
      android.util.Log.e("object", "invalid rotation row: "+row);
407
      return;
408
      }
409

    
410
    //android.util.Log.e("object", "beginning new rotation: axis: "+axis+" row: "+row);
411

    
412
    mRotAxis = axis;
413
    mRotRow  = row;
414

    
415
    mRotationAngleStatic.set0(0.0f);
416

    
417
    for(int i=0; i<NUM_CUBITS; i++)
418
      if( belongsToRotation(i,mRotAxis,mRotRow) )
419
        {
420
        mCubits[i].beginNewRotation(axis);
421
        }
422
     }
423

    
424
///////////////////////////////////////////////////////////////////////////////////////////////////
425

    
426
  public long addNewRotation( int axis, int row, int angle, long durationMillis, EffectListener listener )
427
     {
428
     long effectID=0;
429
     boolean first = true;
430

    
431
     mRotAxis = axis;
432
     mRotRow  = row;
433

    
434
     mRotationAngleStatic.set0(0.0f);
435

    
436
     for(int i=0; i<NUM_CUBITS; i++)
437
       if( belongsToRotation(i,mRotAxis,mRotRow) )
438
         {
439
         mCubits[i].addNewRotation(axis,durationMillis,angle);
440

    
441
         if( first )
442
           {
443
           first = false;
444
           effectID = mCubits[i].setUpCallback(listener);
445
           }
446
         }
447

    
448
     return effectID;
449
     }
450

    
451
///////////////////////////////////////////////////////////////////////////////////////////////////
452

    
453
  public void removeRotationNow()
454
     {
455
     boolean first = true;
456
     Static4D quat = null;
457

    
458
     for(int i=0; i<NUM_CUBITS; i++)
459
       if( belongsToRotation(i,mRotAxis,mRotRow) )
460
         {
461
         if( first )
462
           {
463
           first = false;
464
           quat = mCubits[i].returnRotationQuat(mRotAxis);
465
           }
466

    
467
         mCubits[i].removeRotationNow(quat);
468
         }
469

    
470
     mRotationAngleStatic.set0(0);
471
     }
472

    
473
///////////////////////////////////////////////////////////////////////////////////////////////////
474

    
475
  abstract float getScreenRatio();
476
  abstract VertexEffectSink getSink(int size);
477
  abstract Static3D[] getCubitPositions(int size);
478
  abstract float[] getLegalQuats();
479
  abstract int getNumFaces();
480
  abstract void createFaceTexture(Canvas canvas, Paint paint, int face);
481
  abstract MeshBase createCubitMesh(int cubit, int vertices);
482
  public abstract Static3D[] getRotationAxis();
483
  public abstract int getBasicAngle();
484
  public abstract int returnRowFromOffset(float offset);
485
  }
(4-4/8)