Project

General

Profile

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

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

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2019 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

    
24
import com.google.firebase.crashlytics.FirebaseCrashlytics;
25

    
26
import org.distorted.library.effect.MatrixEffectMove;
27
import org.distorted.library.effect.MatrixEffectQuaternion;
28
import org.distorted.library.effect.MatrixEffectRotate;
29
import org.distorted.library.main.DistortedEffects;
30
import org.distorted.library.message.EffectListener;
31
import org.distorted.library.type.Dynamic1D;
32
import org.distorted.library.type.Static1D;
33
import org.distorted.library.type.Static3D;
34
import org.distorted.library.type.Static4D;
35
import org.distorted.main.RubikSurfaceView;
36

    
37
///////////////////////////////////////////////////////////////////////////////////////////////////
38

    
39
class Cubit
40
  {
41
  private static final int POST_ROTATION_MILLISEC = 500;
42
  private static final Static3D MATRIX_CENTER = new Static3D(0,0,0);
43

    
44
  private final Static3D mOrigPosition;
45

    
46
  private RubikObject mParent;
47
  private Static3D mRotationAxis;
48
  private MatrixEffectRotate mRotateEffect;
49
  private Static3D mCurrentPosition;
50
  private int mNumAxis;
51
  private Dynamic1D mRotationAngle;
52

    
53
  DistortedEffects mEffect;
54
  Static4D mQuatScramble;
55
  float[] mRotationRow;
56

    
57
///////////////////////////////////////////////////////////////////////////////////////////////////
58

    
59
  Cubit(RubikObject parent, Static3D position)
60
    {
61
    float x = position.get0();
62
    float y = position.get1();
63
    float z = position.get2();
64

    
65
    Static3D vector = new Static3D(x,y,z);
66

    
67
    mParent          = parent;
68
    mOrigPosition    = new Static3D(x,y,z);
69
    mQuatScramble    = new Static4D(0,0,0,1);
70
    mRotationAngle   = new Dynamic1D();
71
    mRotationAxis    = new Static3D(1,0,0);
72
    mCurrentPosition = position;
73
    mRotateEffect    = new MatrixEffectRotate(mRotationAngle, mRotationAxis, MATRIX_CENTER);
74

    
75
    mNumAxis     = mParent.ROTATION_AXIS.length;
76
    mRotationRow = new float[mNumAxis];
77
    computeRotationRow();
78

    
79
    mEffect = new DistortedEffects();
80
    mEffect.apply( new MatrixEffectMove(vector) );
81
    mEffect.apply( new MatrixEffectQuaternion(mQuatScramble, MATRIX_CENTER));
82
    mEffect.apply(mRotateEffect);
83
    mEffect.apply(mParent.mQuatAEffect);
84
    mEffect.apply(mParent.mQuatCEffect);
85
    mEffect.apply(mParent.mScaleEffect);
86
    }
87

    
88
///////////////////////////////////////////////////////////////////////////////////////////////////
89
// Because of quatMultiplication, errors can accumulate - so to avoid this, we
90
// correct the value of the 'scramble' quat to what it should be - one of the legal quats from the
91
// list LEGAL_QUATS.
92
//
93
// We also have to remember that the group of unit quaternions is a double-cover of rotations
94
// in 3D ( q represents the same rotation as -q ) - so invert if needed.
95

    
96
  private void normalizeScrambleQuat(Static4D quat)
97
    {
98
    final float MAX_ERROR = 0.0001f;
99

    
100
    float x = quat.get0();
101
    float y = quat.get1();
102
    float z = quat.get2();
103
    float w = quat.get3();
104
    float diff;
105

    
106
    for(float legal: mParent.LEGAL_QUATS)
107
      {
108
      diff = x-legal;
109
      if( diff*diff<MAX_ERROR ) x = legal;
110
      diff = y-legal;
111
      if( diff*diff<MAX_ERROR ) y = legal;
112
      diff = z-legal;
113
      if( diff*diff<MAX_ERROR ) z = legal;
114
      diff = w-legal;
115
      if( diff*diff<MAX_ERROR ) w = legal;
116
      }
117

    
118
    if( w<0 )
119
      {
120
      w = -w;
121
      z = -z;
122
      y = -y;
123
      x = -x;
124
      }
125
    else if( w==0 )
126
      {
127
      if( z<0 )
128
        {
129
        z = -z;
130
        y = -y;
131
        x = -x;
132
        }
133
      else if( z==0 )
134
        {
135
        if( y<0 )
136
          {
137
          y = -y;
138
          x = -x;
139
          }
140
        else if( y==0 )
141
          {
142
          if( x<0 )
143
            {
144
            x = -x;
145
            }
146
          }
147
        }
148
      }
149

    
150
    quat.set(x,y,z,w);
151
    }
152

    
153
///////////////////////////////////////////////////////////////////////////////////////////////////
154

    
155
  private void modifyCurrentPosition(Static4D quat)
156
    {
157
    float cubitCenterX = mCurrentPosition.get0();
158
    float cubitCenterY = mCurrentPosition.get1();
159
    float cubitCenterZ = mCurrentPosition.get2();
160

    
161
    Static4D cubitCenter =  new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
162
    Static4D rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
163

    
164
    float rotatedX = rotatedCenter.get0();
165
    float rotatedY = rotatedCenter.get1();
166
    float rotatedZ = rotatedCenter.get2();
167

    
168
    mCurrentPosition.set(rotatedX, rotatedY, rotatedZ);
169
    mParent.clampPos(mCurrentPosition);
170

    
171
    computeRotationRow();
172
    }
173

    
174
///////////////////////////////////////////////////////////////////////////////////////////////////
175
// cast current position on axis; use mStart and mStep to compute the rotation row for each axis.
176

    
177
  private void computeRotationRow()
178
    {
179
    float tmp;
180
    Static3D axis;
181
    float x = mCurrentPosition.get0();
182
    float y = mCurrentPosition.get1();
183
    float z = mCurrentPosition.get2();
184

    
185
    for(int i=0; i<mNumAxis; i++)
186
      {
187
      axis = mParent.ROTATION_AXIS[i];
188
      tmp = x*axis.get0() + y*axis.get1() + z*axis.get2();
189
      mRotationRow[i] = (tmp-mParent.mStart)/mParent.mStep;
190
      }
191
    }
192

    
193
///////////////////////////////////////////////////////////////////////////////////////////////////
194

    
195
  void savePreferences(SharedPreferences.Editor editor)
196
    {
197
    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
198

    
199
    editor.putFloat("qx_"+number, mQuatScramble.get0());
200
    editor.putFloat("qy_"+number, mQuatScramble.get1());
201
    editor.putFloat("qz_"+number, mQuatScramble.get2());
202
    editor.putFloat("qw_"+number, mQuatScramble.get3());
203
    }
204

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

    
207
  void restorePreferences(SharedPreferences preferences)
208
    {
209
    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
210

    
211
    float qx = preferences.getFloat("qx_"+number, 0.0f);
212
    float qy = preferences.getFloat("qy_"+number, 0.0f);
213
    float qz = preferences.getFloat("qz_"+number, 0.0f);
214
    float qw = preferences.getFloat("qw_"+number, 1.0f);
215

    
216
    mQuatScramble.set(qx,qy,qz,qw);
217
    modifyCurrentPosition(mQuatScramble);
218
    }
219

    
220
///////////////////////////////////////////////////////////////////////////////////////////////////
221
// return if the Cubit, when rotated with its own mQuatScramble, would have looked any different
222
// then if it were rotated by quaternion 'quat'.
223
// No it is not so simple as the quats need to be the same - imagine a 4x4x4 cube where the two
224
// middle squares get interchanged. No visible difference!
225
//
226
// So: this is true iff the cubit
227
// a) is a corner or edge and the quaternions are the same
228
// b) is inside one of the faces and after rotations by both quats it ends up on the same face.
229

    
230
  boolean thereIsNoVisibleDifference(Static4D quat)
231
    {
232
    if ( mQuatScramble.get0()==quat.get0() &&
233
         mQuatScramble.get1()==quat.get1() &&
234
         mQuatScramble.get2()==quat.get2() &&
235
         mQuatScramble.get3()==quat.get3()  ) return true;
236

    
237
    int belongsToHowManyFaces = 0;
238
    int size = mParent.getSize()-1;
239
    float row;
240
    final float MAX_ERROR = 0.01f;
241

    
242
    for(int i=0; i<mNumAxis; i++)
243
      {
244
      row = mRotationRow[i];
245
      if( (row     <MAX_ERROR && row     >-MAX_ERROR) ||
246
          (row-size<MAX_ERROR && row-size>-MAX_ERROR)  ) belongsToHowManyFaces++;
247
      }
248

    
249
    switch(belongsToHowManyFaces)
250
      {
251
      case 0 : return true ;  // 'inside' cubit that does not lie on any face
252
      case 1 :                // cubit that lies inside one of the faces
253
               float cubitCenterX = mCurrentPosition.get0();
254
               float cubitCenterY = mCurrentPosition.get1();
255
               float cubitCenterZ = mCurrentPosition.get2();
256

    
257
               Static4D cubitCenter = new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
258
               Static4D rotated1 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
259
               Static4D rotated2 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, mQuatScramble );
260

    
261
               float row1, row2, row3, row4;
262
               float ax,ay,az;
263
               Static3D axis;
264
               float x1 = rotated1.get0();
265
               float y1 = rotated1.get1();
266
               float z1 = rotated1.get2();
267
               float x2 = rotated2.get0();
268
               float y2 = rotated2.get1();
269
               float z2 = rotated2.get2();
270

    
271
               for(int i=0; i<mNumAxis; i++)
272
                 {
273
                 axis = mParent.ROTATION_AXIS[i];
274
                 ax = axis.get0();
275
                 ay = axis.get1();
276
                 az = axis.get2();
277

    
278
                 row1 = ((x1*ax + y1*ay + z1*az) - mParent.mStart) / mParent.mStep;
279
                 row2 = ((x2*ax + y2*ay + z2*az) - mParent.mStart) / mParent.mStep;
280
                 row3 = row1 - size;
281
                 row4 = row2 - size;
282

    
283
                 if( (row1<MAX_ERROR && row1>-MAX_ERROR && row2<MAX_ERROR && row2>-MAX_ERROR) ||
284
                     (row3<MAX_ERROR && row3>-MAX_ERROR && row4<MAX_ERROR && row4>-MAX_ERROR)  )
285
                   {
286
                   return true;
287
                   }
288
                 }
289
               return false;
290
      default: return false;  // edge or corner
291
      }
292
    }
293

    
294
///////////////////////////////////////////////////////////////////////////////////////////////////
295

    
296
  void removeRotationNow(Static4D quat)
297
    {
298
    mRotationAngle.removeAll();
299
    mQuatScramble.set(RubikSurfaceView.quatMultiply(quat,mQuatScramble));
300
    normalizeScrambleQuat( mQuatScramble );
301
    modifyCurrentPosition(quat);
302
    }
303

    
304
///////////////////////////////////////////////////////////////////////////////////////////////////
305

    
306
  void beginNewRotation(int axis)
307
    {
308
    mRotationAxis.set( mParent.ROTATION_AXIS[axis] );
309
    mRotationAngle.add(mParent.mRotationAngleStatic);
310
    }
311

    
312
///////////////////////////////////////////////////////////////////////////////////////////////////
313

    
314
  void addNewRotation(int axis, long durationMillis, int angle)
315
    {
316
    mRotationAxis.set( mParent.ROTATION_AXIS[axis] );
317
    mRotationAngle.setDuration(durationMillis);
318
    mRotationAngle.resetToBeginning();
319
    mRotationAngle.add(new Static1D(0));
320
    mRotationAngle.add(new Static1D(angle));
321
    }
322

    
323
///////////////////////////////////////////////////////////////////////////////////////////////////
324

    
325
  void resetRotationAngle()
326
    {
327
    mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
328
    mRotationAngle.resetToBeginning();
329
    mRotationAngle.removeAll();
330
    mRotationAngle.add(mParent.mRotationAngleStatic);
331
    mRotationAngle.add(mParent.mRotationAngleMiddle);
332
    mRotationAngle.add(mParent.mRotationAngleFinal);
333
    }
334

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

    
337
  float getAngle()
338
    {
339
    int pointNum = mRotationAngle.getNumPoints();
340

    
341
    if( pointNum>=1 )
342
      {
343
      return mRotationAngle.getPoint(pointNum-1).get0();
344
      }
345
    else
346
      {
347
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
348
      crashlytics.log("points in RotationAngle: "+pointNum);
349
      return 0;
350
      }
351
    }
352

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

    
355
  void solve()
356
    {
357
    mQuatScramble.set(0,0,0,1);
358
    mCurrentPosition.set(mOrigPosition);
359
    computeRotationRow();
360
    }
361

    
362
///////////////////////////////////////////////////////////////////////////////////////////////////
363

    
364
  long setUpCallback(EffectListener listener)
365
    {
366
    mRotateEffect.notifyWhenFinished(listener);
367
    return mRotateEffect.getID();
368
    }
369

    
370
///////////////////////////////////////////////////////////////////////////////////////////////////
371

    
372
  float getDistSquared(float[] point)
373
    {
374
    float dx = mCurrentPosition.get0() - point[0];
375
    float dy = mCurrentPosition.get1() - point[1];
376
    float dz = mCurrentPosition.get2() - point[2];
377

    
378
    return dx*dx + dy*dy + dz*dz;
379
    }
380
}
(1-1/8)