Project

General

Profile

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

magiccube / src / main / java / org / distorted / magic / RubikSurfaceView.java @ 2f1b15da

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.magic;
21

    
22
import android.app.ActivityManager;
23
import android.content.Context;
24
import android.content.SharedPreferences;
25
import android.content.pm.ConfigurationInfo;
26
import android.graphics.PorterDuff;
27
import android.graphics.drawable.Drawable;
28
import android.opengl.GLSurfaceView;
29
import android.support.v4.app.FragmentManager;
30
import android.support.v4.content.ContextCompat;
31
import android.util.AttributeSet;
32
import android.util.DisplayMetrics;
33
import android.view.LayoutInflater;
34
import android.view.MotionEvent;
35
import android.view.View;
36
import android.view.ViewGroup;
37
import android.widget.Button;
38
import android.widget.ImageButton;
39
import android.widget.LinearLayout;
40

    
41
import org.distorted.component.HorizontalNumberPicker;
42
import org.distorted.library.type.Static2D;
43
import org.distorted.library.type.Static4D;
44
import org.distorted.object.RubikCube;
45
import org.distorted.object.RubikCubeMovement;
46

    
47
///////////////////////////////////////////////////////////////////////////////////////////////////
48

    
49
public class RubikSurfaceView extends GLSurfaceView
50
{
51
    public static final int BUTTON_ID_BACK= 1023;
52

    
53
    public static final int MIN_SCRAMBLE =  1;
54
    public static final int DEF_SCRAMBLE =  1;
55
    public static final int MAX_SCRAMBLE = 18;
56

    
57
    // Moving the finger from the middle of the vertical screen to the right edge will rotate a
58
    // given face by SWIPING_SENSITIVITY/2 degrees.
59
    private final static int SWIPING_SENSITIVITY  = 240;
60
    // Moving the finger by 1/12 the distance of min(scrWidth,scrHeight) will start a Rotation.
61
    private final static int ROTATION_SENSITIVITY =  12;
62
    // Every 1/12 the distance of min(scrWidth,scrHeight) the direction of cube rotation will reset.
63
    private final static int DIRECTION_SENSITIVITY=  12;
64

    
65
    private final Static4D CAMERA_POINT = new Static4D(0, 0, RubikRenderer.CAMERA_DISTANCE, 0);
66

    
67
    private RubikRenderer mRenderer;
68
    private RubikCubeMovement mMovement;
69
    private boolean mDragging, mBeginningRotation, mContinuingRotation;
70
    private float mX, mY;
71
    private int mScreenWidth, mScreenHeight, mScreenMin;
72

    
73
    private HorizontalNumberPicker mPicker;
74
    private int mPickerValue;
75
    private RubikState mCurrentState;
76

    
77
    private static int mButton = RubikSize.SIZE3.ordinal();
78
    private static Static4D mQuatCurrent    = new Static4D(0,0,0,1);
79
    private static Static4D mQuatAccumulated= new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
80
    private static Static4D mTempCurrent    = new Static4D(0,0,0,1);
81
    private static Static4D mTempAccumulated= new Static4D(0,0,0,1);
82

    
83
///////////////////////////////////////////////////////////////////////////////////////////////////
84

    
85
    void scramble()
86
      {
87
      int scramble = mPicker.getValue();
88
      mRenderer.scrambleCube(scramble);
89
      }
90

    
91
///////////////////////////////////////////////////////////////////////////////////////////////////
92

    
93
    void savePreferences(SharedPreferences.Editor editor)
94
      {
95
      if( mPicker!=null )
96
        {
97
        editor.putInt("scramble", mPicker.getValue() );
98
        }
99
      }
100

    
101
///////////////////////////////////////////////////////////////////////////////////////////////////
102

    
103
    void restorePreferences(SharedPreferences preferences)
104
      {
105
      mPickerValue= preferences.getInt("scramble", DEF_SCRAMBLE);
106
      }
107

    
108
///////////////////////////////////////////////////////////////////////////////////////////////////
109

    
110
    void setScreenSize(int width, int height)
111
      {
112
      mScreenWidth = width;
113
      mScreenHeight= height;
114

    
115
      mScreenMin = width<height ? width:height;
116
      }
117

    
118
///////////////////////////////////////////////////////////////////////////////////////////////////
119

    
120
    RubikRenderer getRenderer()
121
      {
122
      return mRenderer;
123
      }
124

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

    
127
    static int getRedButton()
128
      {
129
      return mButton;
130
      }
131

    
132
///////////////////////////////////////////////////////////////////////////////////////////////////
133

    
134
    void setQuatAccumulated()
135
      {
136
      mQuatAccumulated.set(mTempAccumulated);
137
      }
138

    
139
///////////////////////////////////////////////////////////////////////////////////////////////////
140

    
141
    void setQuatCurrent()
142
      {
143
      mQuatCurrent.set(mTempCurrent);
144
      }
145

    
146
///////////////////////////////////////////////////////////////////////////////////////////////////
147

    
148
    Static4D getQuatAccumulated()
149
      {
150
      return mQuatAccumulated;
151
      }
152

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

    
155
    Static4D getQuatCurrent()
156
      {
157
      return mQuatCurrent;
158
      }
159

    
160
///////////////////////////////////////////////////////////////////////////////////////////////////
161

    
162
    private Static4D quatFromDrag(float dragX, float dragY)
163
      {
164
      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
165
      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
166
      float axisZ = 0;
167
      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
168

    
169
      if( axisL>0 )
170
        {
171
        axisX /= axisL;
172
        axisY /= axisL;
173
        axisZ /= axisL;
174

    
175
        float ratio = axisL;
176
        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
177

    
178
        float cosA = (float)Math.cos(Math.PI*ratio);
179
        float sinA = (float)Math.sqrt(1-cosA*cosA);
180

    
181
        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
182
        }
183

    
184
      return new Static4D(0f, 0f, 0f, 1f);
185
      }
186

    
187
///////////////////////////////////////////////////////////////////////////////////////////////////
188
// return quat1*quat2
189

    
190
    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
191
      {
192
      float qx = quat1.get0();
193
      float qy = quat1.get1();
194
      float qz = quat1.get2();
195
      float qw = quat1.get3();
196

    
197
      float rx = quat2.get0();
198
      float ry = quat2.get1();
199
      float rz = quat2.get2();
200
      float rw = quat2.get3();
201

    
202
      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
203
      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
204
      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
205
      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
206

    
207
      return new Static4D(tx,ty,tz,tw);
208
      }
209

    
210
///////////////////////////////////////////////////////////////////////////////////////////////////
211
// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
212

    
213
    public static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
214
      {
215
      float qx = quat.get0();
216
      float qy = quat.get1();
217
      float qz = quat.get2();
218
      float qw = quat.get3();
219

    
220
      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
221
      Static4D tmp = quatMultiply(quat,vector);
222

    
223
      return quatMultiply(tmp,quatInverted);
224
      }
225

    
226
///////////////////////////////////////////////////////////////////////////////////////////////////
227
// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
228

    
229
    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
230
      {
231
      float qx = quat.get0();
232
      float qy = quat.get1();
233
      float qz = quat.get2();
234
      float qw = quat.get3();
235

    
236
      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
237
      Static4D tmp = quatMultiply(quatInverted,vector);
238

    
239
      return quatMultiply(tmp,quat);
240
      }
241

    
242
///////////////////////////////////////////////////////////////////////////////////////////////////
243

    
244
    void markButton(int button)
245
      {
246
      mButton = button;
247
      RubikActivity act = (RubikActivity)getContext();
248

    
249
      for(int b=0; b<RubikSize.LENGTH; b++)
250
        {
251
        Drawable d = act.findViewById(b).getBackground();
252

    
253
        if( b==button )
254
          {
255
          d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
256
          }
257
        else
258
          {
259
          d.clearColorFilter();
260
          }
261
        }
262
      }
263

    
264
///////////////////////////////////////////////////////////////////////////////////////////////////
265

    
266
    void switchState(RubikActivity act, RubikState state)
267
      {
268
      if( mCurrentState!=state )
269
        {
270
        switch(state)
271
          {
272
          case MAIN: enterMainState(act); break;
273
          case PLAY: enterPlayState(act); break;
274
          }
275

    
276
        if( mCurrentState!=null )
277
          {
278
          switch(mCurrentState)
279
            {
280
            case MAIN: leaveMainState(act); break;
281
            case PLAY: leavePlayState(act); break;
282
            }
283
          }
284

    
285
        mCurrentState = state;
286
        }
287
      else
288
        {
289
        android.util.Log.e("act", "trying to change into same state "+state);
290
        }
291
      }
292

    
293
///////////////////////////////////////////////////////////////////////////////////////////////////
294

    
295
    private void leaveMainState(RubikActivity act)
296
      {
297
      FragmentManager mana = act.getSupportFragmentManager();
298
      RubikDialogMain diag = (RubikDialogMain) mana.findFragmentByTag(RubikDialogMain.getDialogTag());
299

    
300
      if( diag!=null )
301
        {
302
        diag.dismiss();
303
        }
304
      else
305
        {
306
        android.util.Log.e("act", "cannot find main dialog!");
307
        }
308
      }
309

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

    
312
    private void leavePlayState(RubikActivity act)
313
      {
314
      mPickerValue = mPicker.getValue();
315
      }
316

    
317
///////////////////////////////////////////////////////////////////////////////////////////////////
318

    
319
    private void enterMainState(RubikActivity act)
320
      {
321
      FragmentManager mana = act.getSupportFragmentManager();
322
      RubikDialogMain diag = (RubikDialogMain) mana.findFragmentByTag(RubikDialogMain.getDialogTag());
323

    
324
      if( diag==null )
325
        {
326
        RubikDialogMain diag2 = new RubikDialogMain();
327
        diag2.show( mana, RubikDialogMain.getDialogTag() );
328
        }
329

    
330
      LayoutInflater inflater = act.getLayoutInflater();
331

    
332
      // TOP ////////////////////////////
333
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
334
      layoutTop.removeAllViews();
335
      final View viewTop = inflater.inflate(R.layout.main_title, null);
336
      layoutTop.addView(viewTop);
337

    
338
      // BOT ////////////////////////////
339
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
340
      layoutBot.removeAllViews();
341

    
342
      DisplayMetrics metrics = getResources().getDisplayMetrics();
343
      float scale = metrics.density;
344
      int size = (int)(60*scale +0.5f);
345
      int padding = (int)(5*scale + 0.5f);
346
      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,size,0.5f);
347

    
348
      Button buttonL = new Button(act);
349
      buttonL.setLayoutParams(params);
350
      buttonL.setId(BUTTON_ID_BACK);
351
      buttonL.setPadding(padding,0,padding,0);
352
      buttonL.setText(R.string.back);
353
      buttonL.setOnClickListener(act);
354
      layoutBot.addView(buttonL);
355

    
356
      Button buttonR = new Button(act);
357
      buttonR.setLayoutParams(params);
358
      buttonR.setId(BUTTON_ID_BACK);
359
      buttonR.setPadding(padding,0,padding,0);
360
      buttonR.setText(R.string.exit);
361
      buttonR.setOnClickListener(act);
362
      layoutBot.addView(buttonR);
363

    
364
      buttonL.setVisibility(INVISIBLE);
365
      }
366

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

    
369
    private void enterPlayState(RubikActivity act)
370
      {
371
      LayoutInflater inflater = act.getLayoutInflater();
372

    
373
      // TOP ////////////////////////////
374
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
375
      layoutTop.removeAllViews();
376

    
377
      final View viewTop = inflater.inflate(R.layout.play_title, null);
378
      layoutTop.addView(viewTop);
379

    
380
      // BOT ////////////////////////////
381
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
382
      layoutBot.removeAllViews();
383

    
384
      DisplayMetrics metrics = getResources().getDisplayMetrics();
385
      float scale = metrics.density;
386
      int size = (int)(60*scale +0.5f);
387
      int padding = (int)(3*scale + 0.5f);
388
      ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
389

    
390
      for(int i=0; i<RubikSize.LENGTH; i++)
391
        {
392
        ImageButton button = new ImageButton(act);
393
        button.setLayoutParams(params);
394
        button.setId(i);
395
        button.setPadding(padding,0,padding,0);
396
        int iconID = RubikSize.getSize(i).getIconID();
397
        button.setImageResource(iconID);
398
        button.setOnClickListener(act);
399
        layoutBot.addView(button);
400
        }
401

    
402
      ViewGroup.LayoutParams params2 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,size);
403

    
404
      Button button = new Button(act);
405
      button.setLayoutParams(params2);
406
      button.setId(BUTTON_ID_BACK);
407
      button.setPadding(padding,0,padding,0);
408
      button.setText(R.string.back);
409
      button.setOnClickListener(act);
410
      layoutBot.addView(button);
411

    
412
      markButton(mButton);
413

    
414
      mPicker = act.findViewById(R.id.rubikNumberPicker);
415

    
416
      if( mPicker!=null )
417
        {
418
        mPicker.setMin(MIN_SCRAMBLE);
419
        mPicker.setMax(MAX_SCRAMBLE);
420
        mPicker.setValue(mPickerValue);
421
        }
422
      }
423

    
424
///////////////////////////////////////////////////////////////////////////////////////////////////
425
// PUBLIC API
426
///////////////////////////////////////////////////////////////////////////////////////////////////
427

    
428
    public RubikSurfaceView(Context context, AttributeSet attrs)
429
      {
430
      super(context,attrs);
431

    
432
      if(!isInEditMode())
433
        {
434
        mRenderer = new RubikRenderer(this);
435
        mMovement = new RubikCubeMovement();
436

    
437
        mCurrentState = null;
438

    
439
        final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
440
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
441
        setEGLContextClientVersion( (configurationInfo.reqGlEsVersion>>16) >= 3 ? 3:2 );
442
        setRenderer(mRenderer);
443
        }
444
      }
445

    
446
///////////////////////////////////////////////////////////////////////////////////////////////////
447

    
448
    @Override
449
    public boolean onTouchEvent(MotionEvent event)
450
      {
451
      int action = event.getAction();
452
      float x = (event.getX() - mScreenWidth*0.5f)/mScreenMin;
453
      float y = (mScreenHeight*0.5f -event.getY())/mScreenMin;
454

    
455
      switch(action)
456
         {
457
         case MotionEvent.ACTION_DOWN: mX = x;
458
                                       mY = y;
459

    
460
                                       Static4D touchPoint1 = new Static4D(x, y, 0, 0);
461
                                       Static4D rotatedTouchPoint1= rotateVectorByInvertedQuat(touchPoint1, mQuatAccumulated);
462
                                       Static4D rotatedCamera= rotateVectorByInvertedQuat(CAMERA_POINT, mQuatAccumulated);
463

    
464
                                       if( mMovement.faceTouched(rotatedTouchPoint1, rotatedCamera) )
465
                                         {
466
                                         mDragging           = false;
467
                                         mBeginningRotation  = mRenderer.canRotate();
468
                                         mContinuingRotation = false;
469
                                         }
470
                                       else
471
                                         {
472
                                         mDragging           = mRenderer.canDrag();
473
                                         mBeginningRotation  = false;
474
                                         mContinuingRotation = false;
475
                                         }
476
                                       break;
477
         case MotionEvent.ACTION_MOVE: if( mDragging )
478
                                         {
479
                                         mTempCurrent.set(quatFromDrag(mX-x,y-mY));
480
                                         mRenderer.setQuatCurrentOnNextRender();
481

    
482
                                         if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > 1.0f/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
483
                                           {
484
                                           mX = x;
485
                                           mY = y;
486
                                           mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
487
                                           mTempCurrent.set(0f, 0f, 0f, 1f);
488
                                           mRenderer.setQuatCurrentOnNextRender();
489
                                           mRenderer.setQuatAccumulatedOnNextRender();
490
                                           }
491
                                         }
492
                                       if( mBeginningRotation )
493
                                         {
494
                                         if( (mX-x)*(mX-x)+(mY-y)*(mY-y) > 1.0f/(ROTATION_SENSITIVITY*ROTATION_SENSITIVITY) )
495
                                           {
496
                                           Static4D touchPoint2 = new Static4D(x, y, 0, 0);
497
                                           Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuatAccumulated);
498

    
499
                                           Static2D rot = mMovement.newRotation(rotatedTouchPoint2);
500
                                           RubikCube cube = mRenderer.getCube();
501

    
502
                                           cube.addNewRotation( (int)rot.get0(), (int)(cube.getSize()*rot.get1()) );
503

    
504
                                           mBeginningRotation = false;
505
                                           mContinuingRotation= true;
506
                                           }
507
                                         }
508
                                       else if( mContinuingRotation )
509
                                         {
510
                                         Static4D touchPoint3 = new Static4D(x, y, 0, 0);
511
                                         Static4D rotatedTouchPoint3= rotateVectorByInvertedQuat(touchPoint3, mQuatAccumulated);
512

    
513
                                         float angle = mMovement.continueRotation(rotatedTouchPoint3);
514
                                         mRenderer.getCube().continueRotation(SWIPING_SENSITIVITY*angle);
515
                                         }
516
                                       break;
517
         case MotionEvent.ACTION_UP  : if( mDragging )
518
                                         {
519
                                         mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
520
                                         mTempCurrent.set(0f, 0f, 0f, 1f);
521
                                         mRenderer.setQuatCurrentOnNextRender();
522
                                         mRenderer.setQuatAccumulatedOnNextRender();
523
                                         }
524

    
525
                                       if( mContinuingRotation )
526
                                         {
527
                                         mRenderer.finishRotation();
528
                                         }
529
                                       break;
530
         }
531

    
532
      return true;
533
      }
534
}
535

    
(12-12/12)