Project

General

Profile

« Previous | Next » 

Revision beb325a0

Added by Leszek Koltunski over 4 years ago

Major restructuring - separate the Manipulated Objects (i.e. at the time being - Cubes of various sizes) and the class holding knowledge how those Objects move ( RubikCubeMovement ) into a separate package; remove all knowledge of Objects and the way they move from the main package.

View differences:

src/main/java/org/distorted/magic/RubikSurfaceView.java
22 22
import android.app.ActivityManager;
23 23
import android.content.Context;
24 24
import android.content.pm.ConfigurationInfo;
25
import android.graphics.PorterDuff;
26
import android.graphics.drawable.Drawable;
25 27
import android.opengl.GLSurfaceView;
28
import android.support.v4.content.ContextCompat;
26 29
import android.util.AttributeSet;
30
import android.util.DisplayMetrics;
27 31
import android.view.MotionEvent;
32
import android.view.ViewGroup;
33
import android.widget.Button;
34
import android.widget.ImageButton;
35
import android.widget.LinearLayout;
28 36

  
29 37
import org.distorted.library.type.Static4D;
38
import org.distorted.object.RubikCubeMovement;
30 39

  
31 40
///////////////////////////////////////////////////////////////////////////////////////////////////
32 41

  
33
class RubikSurfaceView extends GLSurfaceView
42
public class RubikSurfaceView extends GLSurfaceView
34 43
{
35
    // Moving the finger from the middle of the vertical screen to the right edge will rotate a
36
    // given face by SWIPING_SENSITIVITY/2 degrees.
37
    private final static int SWIPING_SENSITIVITY  = 240;
38

  
39 44
    // Moving the finger by 1/12 the distance of min(scrWidth,scrHeight) will start a Rotation.
40 45
    private final static int ROTATION_SENSITIVITY =  12;
41 46

  
42 47
    // Every 1/12 the distance of min(scrWidth,scrHeight) the direction of cube rotation will reset.
43 48
    private final static int DIRECTION_SENSITIVITY=  12;
44 49

  
45
    private final static int NONE   =-1;
46
    private final static int FRONT  = 0;  // has to be 6 consecutive ints
47
    private final static int BACK   = 1;  // FRONT ... BOTTOM
48
    private final static int LEFT   = 2;  //
49
    private final static int RIGHT  = 3;  //
50
    private final static int TOP    = 4;  //
51
    private final static int BOTTOM = 5;  //
50
    private RubikRenderer mRenderer;
51
    private RubikCubeMovement mMovement;
52 52

  
53
    private static final int[] VECT = {RubikCube.VECTX,RubikCube.VECTY,RubikCube.VECTZ};
53
    private boolean mInScrambleMode;
54
    private int mButton = RubikSize.SIZE3.ordinal();
54 55

  
55 56
    private boolean mDragging, mBeginningRotation, mContinuingRotation;
56 57
    private int mX, mY;
57
    private int mRotationVect;
58
    private RubikRenderer mRenderer;
59

  
60
    private float[] mPoint, mCamera, mTouchPointCastOntoFace, mDiff, mTouchPoint; // all in screen space
61 58
    private int mLastTouchedFace;
62
    private int mScreenWidth, mScreenHeight, mScreenMin;
63 59
    private float mCameraDistance;
60
    private int mScreenWidth, mScreenHeight, mScreenMin;
64 61

  
65 62
    private static Static4D mQuatCurrent    = new Static4D(0,0,0,1);
66 63
    private static Static4D mQuatAccumulated= new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
......
70 67
///////////////////////////////////////////////////////////////////////////////////////////////////
71 68
// return quat1*quat2
72 69

  
73
    static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
70
    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
74 71
      {
75 72
      float qx = quat1.get1();
76 73
      float qy = quat1.get2();
......
91 88
      }
92 89

  
93 90
///////////////////////////////////////////////////////////////////////////////////////////////////
94
// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
91
// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
95 92

  
96
    static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
93
    public static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
97 94
      {
98 95
      float qx = quat.get1();
99 96
      float qy = quat.get2();
......
101 98
      float qw = quat.get4();
102 99

  
103 100
      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
104
      Static4D tmp = quatMultiply(quatInverted,vector);
101
      Static4D tmp = quatMultiply(quat,vector);
105 102

  
106
      return quatMultiply(tmp,quat);
103
      return quatMultiply(tmp,quatInverted);
107 104
      }
108 105

  
109 106
///////////////////////////////////////////////////////////////////////////////////////////////////
110
// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
107
// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
111 108

  
112
    static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
109
    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
113 110
      {
114 111
      float qx = quat.get1();
115 112
      float qy = quat.get2();
......
117 114
      float qw = quat.get4();
118 115

  
119 116
      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
120
      Static4D tmp = quatMultiply(quat,vector);
121

  
122
      return quatMultiply(tmp,quatInverted);
123
      }
124

  
125
///////////////////////////////////////////////////////////////////////////////////////////////////
126

  
127
    private int faceTouched(int xTouch, int yTouch)
128
      {
129
      float cubeHalfSize= mRenderer.returnCubeSizeInScreenSpace()*0.5f;
130

  
131
      convertTouchPointToScreenSpace(xTouch,yTouch);
132
      convertCameraPointToScreenSpace();
133

  
134
      for(int face=FRONT; face<=BOTTOM; face++)
135
        {
136
        if( faceIsVisible(face,cubeHalfSize) )
137
          {
138
          castTouchPointOntoFace(face,cubeHalfSize, mTouchPointCastOntoFace);
139

  
140
          float qX= (mTouchPointCastOntoFace[0]+cubeHalfSize) / (2*cubeHalfSize);
141
          float qY= (mTouchPointCastOntoFace[1]+cubeHalfSize) / (2*cubeHalfSize);
142
          float qZ= (mTouchPointCastOntoFace[2]+cubeHalfSize) / (2*cubeHalfSize);
143

  
144
          if( qX<=1 && qX>=0 && qY<=1 && qY>=0 && qZ<=1 && qZ>=0 ) return face;
145
          }
146
        }
117
      Static4D tmp = quatMultiply(quatInverted,vector);
147 118

  
148
      return NONE;
119
      return quatMultiply(tmp,quat);
149 120
      }
150 121

  
151 122
///////////////////////////////////////////////////////////////////////////////////////////////////
152 123

  
153
    private void addNewRotation(int x, int y)
124
    void setScreenSize(int width, int height)
154 125
      {
155
      float cubeHalfSize= mRenderer.returnCubeSizeInScreenSpace()*0.5f;
156

  
157
      convertTouchPointToScreenSpace(x,y);
158
      castTouchPointOntoFace(mLastTouchedFace,cubeHalfSize,mDiff);
159

  
160
      mDiff[0] -= mTouchPointCastOntoFace[0];
161
      mDiff[1] -= mTouchPointCastOntoFace[1];
162
      mDiff[2] -= mTouchPointCastOntoFace[2];
163

  
164
      int xAxis = retFaceXaxis(mLastTouchedFace);
165
      int yAxis = retFaceYaxis(mLastTouchedFace);
166
      mRotationVect = (isVertical( mDiff[xAxis], mDiff[yAxis]) ? VECT[xAxis]:VECT[yAxis]);
167
      float offset= (mTouchPointCastOntoFace[mRotationVect]+cubeHalfSize)/(2*cubeHalfSize);
168

  
169
      mTouchPoint[0] = mPoint[0];
170
      mTouchPoint[1] = mPoint[1];
171
      mTouchPoint[2] = mPoint[2];
126
      mScreenWidth = width;
127
      mScreenHeight= height;
172 128

  
173
      RubikCube cube = mRenderer.getCube();
174
      cube.addNewRotation(mRotationVect, (int)(cube.getSize()*offset) );
129
      mScreenMin = width<height ? width:height;
175 130
      }
176 131

  
177 132
///////////////////////////////////////////////////////////////////////////////////////////////////
178 133

  
179
    private boolean isVertical(float x, float y)
134
    void setCameraDist(float distance)
180 135
      {
181
      return (y>x) ? (y>=-x) : (y< -x);
136
      mCameraDistance = distance;
182 137
      }
183 138

  
184 139
///////////////////////////////////////////////////////////////////////////////////////////////////
185 140

  
186
    private void continueRotation(int x, int y)
141
    RubikRenderer getRenderer()
187 142
      {
188
      convertTouchPointToScreenSpace(x,y);
189

  
190
      mDiff[0] = mPoint[0]-mTouchPoint[0];
191
      mDiff[1] = mPoint[1]-mTouchPoint[1];
192
      mDiff[2] = mPoint[2]-mTouchPoint[2];
193

  
194
      int xAxis= retFaceXaxis(mLastTouchedFace);
195
      int yAxis= retFaceYaxis(mLastTouchedFace);
196
      int sign = retFaceRotationSign(mLastTouchedFace);
197
      float angle = (mRotationVect==xAxis ? mDiff[yAxis] : -mDiff[xAxis]);
198

  
199
      mRenderer.getCube().continueRotation(SWIPING_SENSITIVITY*sign*angle/mScreenMin);
143
      return mRenderer;
200 144
      }
201 145

  
202 146
///////////////////////////////////////////////////////////////////////////////////////////////////
203 147

  
204
    private void finishRotation()
148
    int getRedButton()
205 149
      {
206
      mRenderer.finishRotation();
150
      return mButton;
207 151
      }
208 152

  
209 153
///////////////////////////////////////////////////////////////////////////////////////////////////
210 154

  
211
    private Static4D quatFromDrag(float dragX, float dragY)
155
    void markButton(int button)
212 156
      {
213
      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
214
      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
215
      float axisZ = 0;
216
      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
157
      mButton = button;
158
      RubikActivity act = (RubikActivity)getContext();
217 159

  
218
      if( axisL>0 )
160
      for(int b=0; b<RubikSize.LENGTH; b++)
219 161
        {
220
        axisX /= axisL;
221
        axisY /= axisL;
222
        axisZ /= axisL;
162
        Drawable d = act.findViewById(b).getBackground();
223 163

  
224
        float ratio = axisL/mScreenMin;
225
        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
226

  
227
        float cosA = (float)Math.cos(Math.PI*ratio);
228
        float sinA = (float)Math.sqrt(1-cosA*cosA);
229

  
230
        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
164
        if( b==button )
165
          {
166
          d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
167
          }
168
        else
169
          {
170
          d.clearColorFilter();
171
          }
231 172
        }
232

  
233
      return new Static4D(0f, 0f, 0f, 1f);
234 173
      }
235 174

  
236 175
///////////////////////////////////////////////////////////////////////////////////////////////////
237 176

  
238
    private boolean faceIsVisible(int face, float cubeHalfSize)
177
    void addSizeButtons(RubikActivity act)
239 178
      {
240
      int sign = retFaceSign(face);
241
      int zAxis= retFaceZaxis(face);
242

  
243
      return sign*mCamera[zAxis] > cubeHalfSize;
244
      }
245

  
246
///////////////////////////////////////////////////////////////////////////////////////////////////
247

  
248
    private void convertTouchPointToScreenSpace(int x, int y)
249
      {
250
      float halfScrWidth  = mScreenWidth *0.5f;
251
      float halfScrHeight = mScreenHeight*0.5f;
252
      Static4D touchPoint = new Static4D(x-halfScrWidth, halfScrHeight-y, 0, 0);
253
      Static4D rotatedTouchPoint= rotateVectorByInvertedQuat(touchPoint, mQuatAccumulated);
254

  
255
      mPoint[0] = rotatedTouchPoint.get1();
256
      mPoint[1] = rotatedTouchPoint.get2();
257
      mPoint[2] = rotatedTouchPoint.get3();
179
      LinearLayout layout = act.findViewById(R.id.sizeLayout);
180
      DisplayMetrics metrics = getResources().getDisplayMetrics();
181
      float scale = metrics.density;
182
      int size = (int)(64*scale +0.5f);
183
      int padding = (int)(3*scale + 0.5f);
184
      ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
185

  
186
      for(int i=0; i<RubikSize.LENGTH; i++)
187
        {
188
        ImageButton button = new ImageButton(act);
189
        button.setLayoutParams(params);
190
        button.setId(i);
191
        button.setPadding(padding,0,padding,0);
192
        int iconID = RubikSize.getSize(i).getIconID();
193
        button.setImageResource(iconID);
194
        button.setOnClickListener(act);
195
        layout.addView(button);
196
        }
258 197
      }
259 198

  
260 199
///////////////////////////////////////////////////////////////////////////////////////////////////
261 200

  
262
    private void convertCameraPointToScreenSpace()
201
    void enterScrambleMode()
263 202
      {
264
      Static4D cameraPoint = new Static4D(0, 0, mCameraDistance, 0);
265
      Static4D rotatedCamera= rotateVectorByInvertedQuat(cameraPoint, mQuatAccumulated);
266

  
267
      mCamera[0] = rotatedCamera.get1();
268
      mCamera[1] = rotatedCamera.get2();
269
      mCamera[2] = rotatedCamera.get3();
270
      }
203
      if( !mInScrambleMode )
204
        {
205
        mInScrambleMode = true;
271 206

  
272
///////////////////////////////////////////////////////////////////////////////////////////////////
273
// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
274
// cast this touch point onto the surface defined by the 'face' and write the cast coords to 'output'.
275
// Center of the 'face' = (0,0), third coord always +- cubeHalfSize.
207
        android.util.Log.e("view", "entering scramble mode");
276 208

  
277
    private void castTouchPointOntoFace(int face, float cubeHalfSize, float[] output)
278
      {
279
      int sign = retFaceSign(face);
280
      int zAxis= retFaceZaxis(face);
281
      float diff = mPoint[zAxis]-mCamera[zAxis];
209
        RubikActivity act = (RubikActivity)getContext();
282 210

  
283
      float ratio =  diff!=0.0f ? (sign*cubeHalfSize-mCamera[zAxis])/diff : 0.0f;
211
        Button scrambleButt = act.findViewById(R.id.rubikScramble);
284 212

  
285
      output[0] = (mPoint[0]-mCamera[0])*ratio + mCamera[0];
286
      output[1] = (mPoint[1]-mCamera[1])*ratio + mCamera[1];
287
      output[2] = (mPoint[2]-mCamera[2])*ratio + mCamera[2];
213
        if( scrambleButt!=null )
214
          {
215
          scrambleButt.setClickable(false);
216
          scrambleButt.setVisibility(INVISIBLE);
217
          }
218
        else
219
          {
220
          android.util.Log.e("view", "button null!");
221
          }
222
        }
288 223
      }
289 224

  
290 225
///////////////////////////////////////////////////////////////////////////////////////////////////
291 226

  
292
    private int retFaceSign(int face)
227
    void leaveScrambleMode()
293 228
      {
294
      return (face==FRONT || face==RIGHT || face==TOP) ? 1:-1;
295
      }
229
      if( mInScrambleMode )
230
        {
231
        mInScrambleMode = false;
296 232

  
297
///////////////////////////////////////////////////////////////////////////////////////////////////
233
        android.util.Log.e("view", "leaving scramble mode");
298 234

  
299
    private int retFaceRotationSign(int face)
300
      {
301
      return (face==BACK || face==RIGHT || face==TOP) ? 1:-1;
302
      }
235
        RubikActivity act = (RubikActivity)getContext();
303 236

  
304
///////////////////////////////////////////////////////////////////////////////////////////////////
305
// retFace{X,Y,Z}axis: 3 functions which return which real AXIS gets mapped to which when we look
306
// directly at a given face. For example, when we look at the RIGHT face of the cube (with TOP still
307
// in the top) then the 'real' X axis becomes the 'Z' axis, thus retFaceZaxis(RIGHT) = VECTX.
237
        Button scrambleButt = act.findViewById(R.id.rubikScramble);
308 238

  
309
    private int retFaceXaxis(int face)
310
      {
311
      switch(face)
312
        {
313
        case FRONT :
314
        case BACK  : return RubikCube.VECTX;
315
        case LEFT  :
316
        case RIGHT : return RubikCube.VECTZ;
317
        case TOP   :
318
        case BOTTOM: return RubikCube.VECTX;
239
        if( scrambleButt!=null )
240
          {
241
          scrambleButt.setClickable(true);
242
          scrambleButt.setVisibility(VISIBLE);
243
          }
244
        else
245
          {
246
          android.util.Log.e("view", "button null!");
247
          }
319 248
        }
320

  
321
      return -1;
322 249
      }
323 250

  
324 251
///////////////////////////////////////////////////////////////////////////////////////////////////
325 252

  
326
    private int retFaceYaxis(int face)
253
    void leaveScrambleModeNonUI()
327 254
      {
328
      switch(face)
329
        {
330
        case FRONT :
331
        case BACK  : return RubikCube.VECTY;
332
        case LEFT  :
333
        case RIGHT : return RubikCube.VECTY;
334
        case TOP   :
335
        case BOTTOM: return RubikCube.VECTZ;
336
        }
255
      RubikActivity act = (RubikActivity)getContext();
337 256

  
338
      return -1;
339
      }
340

  
341
///////////////////////////////////////////////////////////////////////////////////////////////////
342

  
343
    private int retFaceZaxis(int face)
344
      {
345
      switch(face)
257
      act.runOnUiThread(new Runnable()
346 258
        {
347
        case FRONT :
348
        case BACK  : return RubikCube.VECTZ;
349
        case LEFT  :
350
        case RIGHT : return RubikCube.VECTX;
351
        case TOP   :
352
        case BOTTOM: return RubikCube.VECTY;
353
        }
354

  
355
      return -1;
259
        @Override
260
        public void run()
261
          {
262
          leaveScrambleMode();
263
          }
264
        });
356 265
      }
357 266

  
358 267
///////////////////////////////////////////////////////////////////////////////////////////////////
......
385 294

  
386 295
///////////////////////////////////////////////////////////////////////////////////////////////////
387 296

  
388
    RubikRenderer getRenderer()
297
    private Static4D quatFromDrag(float dragX, float dragY)
389 298
      {
390
      return mRenderer;
391
      }
299
      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
300
      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
301
      float axisZ = 0;
302
      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
392 303

  
393
///////////////////////////////////////////////////////////////////////////////////////////////////
304
      if( axisL>0 )
305
        {
306
        axisX /= axisL;
307
        axisY /= axisL;
308
        axisZ /= axisL;
394 309

  
395
    void setScreenSize(int width, int height)
396
      {
397
      mScreenWidth = width;
398
      mScreenHeight= height;
310
        float ratio = axisL/mScreenMin;
311
        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
399 312

  
400
      mScreenMin = width<height ? width:height;
313
        float cosA = (float)Math.cos(Math.PI*ratio);
314
        float sinA = (float)Math.sqrt(1-cosA*cosA);
315

  
316
        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
317
        }
318

  
319
      return new Static4D(0f, 0f, 0f, 1f);
401 320
      }
402 321

  
403 322
///////////////////////////////////////////////////////////////////////////////////////////////////
404 323

  
405
    void setCameraDist(float distance)
324
    void setMovement(RubikCubeMovement movement)
406 325
      {
407
      mCameraDistance = distance;
326
      mMovement = movement;
408 327
      }
409 328

  
410 329
///////////////////////////////////////////////////////////////////////////////////////////////////
......
417 336

  
418 337
      if(!isInEditMode())
419 338
        {
420
        mRotationVect = VECT[0];
421

  
422
        mPoint = new float[3];
423
        mCamera= new float[3];
424
        mDiff  = new float[3];
425
        mTouchPoint = new float[3];
426
        mTouchPointCastOntoFace = new float[3];
427

  
428
        mScreenWidth = mScreenHeight = mScreenMin = 0;
429

  
339
        mInScrambleMode = false;
430 340
        mRenderer = new RubikRenderer(this);
431 341

  
432 342
        final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
......
449 359
         {
450 360
         case MotionEvent.ACTION_DOWN: mX = x;
451 361
                                       mY = y;
452
                                       mLastTouchedFace = faceTouched(x,y);
362
                                       mLastTouchedFace = mMovement.faceTouched(mQuatAccumulated,mCameraDistance, x,y, mScreenWidth, mScreenHeight);
453 363

  
454
                                       if( mLastTouchedFace != NONE )
364
                                       if( mLastTouchedFace != RubikCubeMovement.NONE )
455 365
                                         {
456 366
                                         mDragging           = false;
457 367
                                         mBeginningRotation  = mRenderer.canRotate();
......
487 397

  
488 398
                                         if( (mX-x)*(mX-x)+(mY-y)*(mY-y) > minimumDistToStartRotating )
489 399
                                           {
490
                                           addNewRotation(x,y);
400
                                           mMovement.addNewRotation(mQuatAccumulated,mLastTouchedFace, x,y, mScreenWidth, mScreenHeight);
491 401
                                           mBeginningRotation = false;
492 402
                                           mContinuingRotation= true;
493 403
                                           }
494 404
                                         }
495 405
                                       else if( mContinuingRotation )
496 406
                                         {
497
                                         continueRotation(x,y);
407
                                         mMovement.continueRotation(mQuatAccumulated,mLastTouchedFace, x,y, mScreenWidth, mScreenHeight);
498 408
                                         }
499 409
                                       break;
500 410
         case MotionEvent.ACTION_UP  : if( mDragging )
......
507 417

  
508 418
                                       if( mContinuingRotation )
509 419
                                         {
510
                                         finishRotation();
420
                                         mRenderer.finishRotation();
511 421
                                         }
512

  
513 422
                                       break;
514 423
         }
515 424

  

Also available in: Unified diff