Project

General

Profile

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

magiccube / src / main / java / org / distorted / objects / Cubit.java @ 001cc0e4

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 org.distorted.library.effect.MatrixEffectMove;
25
import org.distorted.library.effect.MatrixEffectQuaternion;
26
import org.distorted.library.effect.MatrixEffectRotate;
27
import org.distorted.library.main.DistortedEffects;
28
import org.distorted.library.main.DistortedNode;
29
import org.distorted.library.mesh.MeshBase;
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 matrCenter = new Static3D(0,0,0);
43

    
44
  private final Static3D mOrigPosition;
45

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

    
54
  DistortedNode mNode;
55
  DistortedEffects mEffect;
56
  Static4D mQuatScramble;
57
  float[] mRotationRow;
58

    
59
///////////////////////////////////////////////////////////////////////////////////////////////////
60
// Because of quatMultiplication, errors can accumulate - so to avoid this, we
61
// correct the value of the 'scramble' quat to what it should be - one of the legal quats from the
62
// list LEGAL_QUATS.
63
//
64
// We also have to remember that the group of unit quaternions is a double-cover of rotations
65
// in 3D ( q represents the same rotation as -q ) - so invert if needed.
66

    
67
  private void normalizeScrambleQuat(Static4D quat)
68
    {
69
    final float MAX_ERROR = 0.0001f;
70

    
71
    float x = quat.get0();
72
    float y = quat.get1();
73
    float z = quat.get2();
74
    float w = quat.get3();
75
    float diff;
76

    
77
    for(float legal: mParent.LEGAL_QUATS)
78
      {
79
      diff = x-legal;
80
      if( diff*diff<MAX_ERROR ) x = legal;
81
      diff = y-legal;
82
      if( diff*diff<MAX_ERROR ) y = legal;
83
      diff = z-legal;
84
      if( diff*diff<MAX_ERROR ) z = legal;
85
      diff = w-legal;
86
      if( diff*diff<MAX_ERROR ) w = legal;
87
      }
88

    
89
    if( w<0 )
90
      {
91
      w = -w;
92
      z = -z;
93
      y = -y;
94
      x = -x;
95
      }
96
    else if( w==0 )
97
      {
98
      if( z<0 )
99
        {
100
        z = -z;
101
        y = -y;
102
        x = -x;
103
        }
104
      else if( z==0 )
105
        {
106
        if( y<0 )
107
          {
108
          y = -y;
109
          x = -x;
110
          }
111
        else if( y==0 )
112
          {
113
          if( x<0 )
114
            {
115
            x = -x;
116
            }
117
          }
118
        }
119
      }
120

    
121
    quat.set(x,y,z,w);
122
    }
123

    
124
///////////////////////////////////////////////////////////////////////////////////////////////////
125

    
126
  private void modifyCurrentPosition(Static4D quat)
127
    {
128
    float cubitCenterX = mCurrentPosition.get0();
129
    float cubitCenterY = mCurrentPosition.get1();
130
    float cubitCenterZ = mCurrentPosition.get2();
131

    
132
    Static4D cubitCenter =  new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
133
    Static4D rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
134

    
135
    float rotatedX = rotatedCenter.get0();
136
    float rotatedY = rotatedCenter.get1();
137
    float rotatedZ = rotatedCenter.get2();
138

    
139
    mCurrentPosition.set(rotatedX, rotatedY, rotatedZ);
140
    mParent.clampPos(mCurrentPosition);
141

    
142
    computeRotationRow();
143
    }
144

    
145
///////////////////////////////////////////////////////////////////////////////////////////////////
146
// cast current position on axis; use mStart and mStep to compute the rotation row for each axis.
147

    
148
  private void computeRotationRow()
149
    {
150
    float tmp;
151
    Static3D axis;
152
    float x = mCurrentPosition.get0();
153
    float y = mCurrentPosition.get1();
154
    float z = mCurrentPosition.get2();
155

    
156
    for(int i=0; i<mNumAxis; i++)
157
      {
158
      axis = mParent.ROTATION_AXIS[i];
159
      tmp = x*axis.get0() + y*axis.get1() + z*axis.get2();
160
      mRotationRow[i] = (tmp-mParent.mStart)/mParent.mStep;
161
      }
162
    }
163

    
164
///////////////////////////////////////////////////////////////////////////////////////////////////
165

    
166
  Cubit(RubikObject parent, MeshBase mesh, Static3D position)
167
    {
168
    float x = position.get0();
169
    float y = position.get1();
170
    float z = position.get2();
171

    
172
    Static3D vector = new Static3D(x,y,z);
173

    
174
    mParent          = parent;
175
    mMesh            = mesh;
176
    mOrigPosition    = new Static3D(x,y,z);
177
    mQuatScramble    = new Static4D(0,0,0,1);
178
    mRotationAngle   = new Dynamic1D();
179
    mRotationAxis    = new Static3D(1,0,0);
180
    mCurrentPosition = position;
181
    mRotateEffect    = new MatrixEffectRotate(mRotationAngle, mRotationAxis, matrCenter);
182

    
183
    mNumAxis     = mParent.ROTATION_AXIS.length;
184
    mRotationRow = new float[mNumAxis];
185
    computeRotationRow();
186

    
187
    mEffect = new DistortedEffects();
188
    mEffect.apply(mParent.mSinkEffect);
189
    mEffect.apply( new MatrixEffectMove(vector) );
190
    mEffect.apply( new MatrixEffectQuaternion(mQuatScramble, matrCenter));
191
    mEffect.apply(mRotateEffect);
192
    mEffect.apply(mParent.mQuatAEffect);
193
    mEffect.apply(mParent.mQuatCEffect);
194
    mEffect.apply(mParent.mScaleEffect);
195

    
196
    mNode = new DistortedNode(mParent.mTexture,mEffect,mMesh);
197
    }
198

    
199
///////////////////////////////////////////////////////////////////////////////////////////////////
200

    
201
  void savePreferences(SharedPreferences.Editor editor)
202
    {
203
    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
204

    
205
    editor.putFloat("qx_"+number, mQuatScramble.get0());
206
    editor.putFloat("qy_"+number, mQuatScramble.get1());
207
    editor.putFloat("qz_"+number, mQuatScramble.get2());
208
    editor.putFloat("qw_"+number, mQuatScramble.get3());
209
    }
210

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

    
213
  void restorePreferences(SharedPreferences preferences)
214
    {
215
    String number = mOrigPosition.get0()+"_"+mOrigPosition.get1()+"_"+mOrigPosition.get2();
216

    
217
    float qx = preferences.getFloat("qx_"+number, 0.0f);
218
    float qy = preferences.getFloat("qy_"+number, 0.0f);
219
    float qz = preferences.getFloat("qz_"+number, 0.0f);
220
    float qw = preferences.getFloat("qw_"+number, 1.0f);
221

    
222
    mQuatScramble.set(qx,qy,qz,qw);
223
    modifyCurrentPosition(mQuatScramble);
224
    }
225

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

    
236
  boolean thereIsNoVisibleDifference(Static4D quat)
237
    {
238
    if ( mQuatScramble.get0()==quat.get0() &&
239
         mQuatScramble.get1()==quat.get1() &&
240
         mQuatScramble.get2()==quat.get2() &&
241
         mQuatScramble.get3()==quat.get3()  ) return true;
242

    
243
    int belongsToHowManyFaces = 0;
244
    int size = mParent.getSize()-1;
245
    float row;
246
    final float MAX_ERROR = 0.01f;
247

    
248
    for(int i=0; i<mNumAxis; i++)
249
      {
250
      row = mRotationRow[i];
251
      if( (row     <MAX_ERROR && row     >-MAX_ERROR) ||
252
          (row-size<MAX_ERROR && row-size>-MAX_ERROR)  ) belongsToHowManyFaces++;
253
      }
254

    
255
    switch(belongsToHowManyFaces)
256
      {
257
      case 0 : return true ;  // 'inside' cubit that does not lie on any face
258
      case 1 :                // cubit that lies inside one of the faces
259
               float cubitCenterX = mCurrentPosition.get0();
260
               float cubitCenterY = mCurrentPosition.get1();
261
               float cubitCenterZ = mCurrentPosition.get2();
262

    
263
               Static4D cubitCenter = new Static4D(cubitCenterX, cubitCenterY, cubitCenterZ, 0);
264
               Static4D rotated1 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
265
               Static4D rotated2 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, mQuatScramble );
266

    
267
               float row1, row2, row3, row4;
268
               float ax,ay,az;
269
               Static3D axis;
270
               float x1 = rotated1.get0();
271
               float y1 = rotated1.get1();
272
               float z1 = rotated1.get2();
273
               float x2 = rotated2.get0();
274
               float y2 = rotated2.get1();
275
               float z2 = rotated2.get2();
276

    
277
               for(int i=0; i<mNumAxis; i++)
278
                 {
279
                 axis = mParent.ROTATION_AXIS[i];
280
                 ax = axis.get0();
281
                 ay = axis.get1();
282
                 az = axis.get2();
283

    
284
                 row1 = ((x1*ax + y1*ay + z1*az) - mParent.mStart) / mParent.mStep;
285
                 row2 = ((x2*ax + y2*ay + z2*az) - mParent.mStart) / mParent.mStep;
286
                 row3 = row1 - size;
287
                 row4 = row2 - size;
288

    
289
                 if( (row1<MAX_ERROR && row1>-MAX_ERROR && row2<MAX_ERROR && row2>-MAX_ERROR) ||
290
                     (row3<MAX_ERROR && row3>-MAX_ERROR && row4<MAX_ERROR && row4>-MAX_ERROR)  )
291
                   {
292
                   return true;
293
                   }
294
                 }
295
               return false;
296
      default: return false;  // edge or corner
297
      }
298
    }
299

    
300
///////////////////////////////////////////////////////////////////////////////////////////////////
301

    
302
  void removeRotationNow(Static4D quat)
303
    {
304
    mRotationAngle.removeAll();
305
    mQuatScramble.set(RubikSurfaceView.quatMultiply(quat,mQuatScramble));
306
    normalizeScrambleQuat( mQuatScramble );
307
    modifyCurrentPosition(quat);
308
    }
309

    
310
///////////////////////////////////////////////////////////////////////////////////////////////////
311

    
312
  void beginNewRotation(int axis)
313
    {
314
    mRotationAxis.set( mParent.ROTATION_AXIS[axis] );
315
    mRotationAngle.add(mParent.mRotationAngleStatic);
316
    }
317

    
318
///////////////////////////////////////////////////////////////////////////////////////////////////
319

    
320
  void addNewRotation(int axis, long durationMillis, int angle)
321
    {
322
    mRotationAxis.set( mParent.ROTATION_AXIS[axis] );
323
    mRotationAngle.setDuration(durationMillis);
324
    mRotationAngle.resetToBeginning();
325
    mRotationAngle.add(new Static1D(0));
326
    mRotationAngle.add(new Static1D(angle));
327
    }
328

    
329
///////////////////////////////////////////////////////////////////////////////////////////////////
330

    
331
  void resetRotationAngle()
332
    {
333
    mRotationAngle.setDuration(POST_ROTATION_MILLISEC);
334
    mRotationAngle.resetToBeginning();
335
    mRotationAngle.removeAll();
336
    mRotationAngle.add(mParent.mRotationAngleStatic);
337
    mRotationAngle.add(mParent.mRotationAngleMiddle);
338
    mRotationAngle.add(mParent.mRotationAngleFinal);
339
    }
340

    
341
///////////////////////////////////////////////////////////////////////////////////////////////////
342
// all DistortedTextures, DistortedNodes, DistortedFramebuffers, DistortedScreens and all types of
343
// Meshes HAVE TO be markedForDeletion when they are no longer needed- otherwise we have a major
344
// memory leak.
345

    
346
  void releaseResources()
347
    {
348
    mMesh.markForDeletion();
349
    mNode.markForDeletion();
350
    }
351

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

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

    
361
///////////////////////////////////////////////////////////////////////////////////////////////////
362

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

    
369
///////////////////////////////////////////////////////////////////////////////////////////////////
370

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

    
377
    return dx*dx + dy*dy + dz*dz;
378
    }
379

    
380
///////////////////////////////////////////////////////////////////////////////////////////////////
381

    
382
  int getColorIndex(int face)
383
    {
384
    Static4D texMap = mMesh.getTextureMap(face);
385
    return (int)(texMap.get0() / texMap.get2());
386
    }
387

    
388
///////////////////////////////////////////////////////////////////////////////////////////////////
389

    
390
  MeshBase getMesh()
391
    {
392
    return mMesh;
393
    }
394
}
(1-1/8)