Project

General

Profile

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

magiccube / src / main / java / org / distorted / magic / RubikSurfaceView.java @ 6918030e

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 enterState(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==RubikState.PLAY )
277
          {
278
          mPickerValue = mPicker.getValue();
279
          }
280
        if( mCurrentState==RubikState.MAIN )
281
          {
282
          FragmentManager mana = act.getSupportFragmentManager();
283
          RubikDialogMain diag = (RubikDialogMain) mana.findFragmentByTag(RubikDialogMain.getDialogTag());
284

    
285
          if( diag!=null )
286
            {
287
            diag.dismiss();
288
            }
289
          else
290
            {
291
            android.util.Log.e("act", "cannot find main dialog!");
292
            }
293
          }
294
        if( state==RubikState.MAIN )
295
          {
296
          FragmentManager mana = act.getSupportFragmentManager();
297
          RubikDialogMain diag = (RubikDialogMain) mana.findFragmentByTag(RubikDialogMain.getDialogTag());
298

    
299
          if( diag==null )
300
            {
301
            RubikDialogMain diag2 = new RubikDialogMain();
302
            diag2.show( mana, RubikDialogMain.getDialogTag() );
303
            }
304
          }
305

    
306
        mCurrentState = state;
307
        }
308
      else
309
        {
310
        android.util.Log.e("act", "trying to change into same state "+state);
311
        }
312
      }
313

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

    
316
    private void enterMainState(RubikActivity act)
317
      {
318
      LayoutInflater inflater = act.getLayoutInflater();
319

    
320
      // TOP ////////////////////////////
321
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
322
      layoutTop.removeAllViews();
323
      final View viewTop = inflater.inflate(R.layout.main_title, null);
324
      layoutTop.addView(viewTop);
325

    
326
      // BOT ////////////////////////////
327
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
328
      layoutBot.removeAllViews();
329

    
330
      DisplayMetrics metrics = getResources().getDisplayMetrics();
331
      float scale = metrics.density;
332
      int size = (int)(60*scale +0.5f);
333
      int padding = (int)(5*scale + 0.5f);
334
      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,size,0.5f);
335

    
336
      Button buttonL = new Button(act);
337
      buttonL.setLayoutParams(params);
338
      buttonL.setId(BUTTON_ID_BACK);
339
      buttonL.setPadding(padding,0,padding,0);
340
      buttonL.setText(R.string.back);
341
      buttonL.setOnClickListener(act);
342
      layoutBot.addView(buttonL);
343

    
344
      Button buttonR = new Button(act);
345
      buttonR.setLayoutParams(params);
346
      buttonR.setId(BUTTON_ID_BACK);
347
      buttonR.setPadding(padding,0,padding,0);
348
      buttonR.setText(R.string.exit);
349
      buttonR.setOnClickListener(act);
350
      layoutBot.addView(buttonR);
351

    
352
      buttonL.setVisibility(INVISIBLE);
353
      }
354

    
355
///////////////////////////////////////////////////////////////////////////////////////////////////
356

    
357
    private void enterPlayState(RubikActivity act)
358
      {
359
      LayoutInflater inflater = act.getLayoutInflater();
360

    
361
      // TOP ////////////////////////////
362
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
363
      layoutTop.removeAllViews();
364

    
365
      final View viewTop = inflater.inflate(R.layout.play_title, null);
366
      layoutTop.addView(viewTop);
367

    
368
      // BOT ////////////////////////////
369
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
370
      layoutBot.removeAllViews();
371

    
372
      DisplayMetrics metrics = getResources().getDisplayMetrics();
373
      float scale = metrics.density;
374
      int size = (int)(60*scale +0.5f);
375
      int padding = (int)(3*scale + 0.5f);
376
      ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
377

    
378
      for(int i=0; i<RubikSize.LENGTH; i++)
379
        {
380
        ImageButton button = new ImageButton(act);
381
        button.setLayoutParams(params);
382
        button.setId(i);
383
        button.setPadding(padding,0,padding,0);
384
        int iconID = RubikSize.getSize(i).getIconID();
385
        button.setImageResource(iconID);
386
        button.setOnClickListener(act);
387
        layoutBot.addView(button);
388
        }
389

    
390
      ViewGroup.LayoutParams params2 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,size);
391

    
392
      Button button = new Button(act);
393
      button.setLayoutParams(params2);
394
      button.setId(BUTTON_ID_BACK);
395
      button.setPadding(padding,0,padding,0);
396
      button.setText(R.string.back);
397
      button.setOnClickListener(act);
398
      layoutBot.addView(button);
399

    
400
      markButton(mButton);
401

    
402
      mPicker = act.findViewById(R.id.rubikNumberPicker);
403

    
404
      if( mPicker!=null )
405
        {
406
        mPicker.setMin(MIN_SCRAMBLE);
407
        mPicker.setMax(MAX_SCRAMBLE);
408
        mPicker.setValue(mPickerValue);
409
        }
410
      }
411

    
412
///////////////////////////////////////////////////////////////////////////////////////////////////
413
// PUBLIC API
414
///////////////////////////////////////////////////////////////////////////////////////////////////
415

    
416
    public RubikSurfaceView(Context context, AttributeSet attrs)
417
      {
418
      super(context,attrs);
419

    
420
      if(!isInEditMode())
421
        {
422
        mRenderer = new RubikRenderer(this);
423
        mMovement = new RubikCubeMovement();
424

    
425
        mCurrentState = null;
426

    
427
        final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
428
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
429
        setEGLContextClientVersion( (configurationInfo.reqGlEsVersion>>16) >= 3 ? 3:2 );
430
        setRenderer(mRenderer);
431
        }
432
      }
433

    
434
///////////////////////////////////////////////////////////////////////////////////////////////////
435

    
436
    @Override
437
    public boolean onTouchEvent(MotionEvent event)
438
      {
439
      int action = event.getAction();
440
      float x = (event.getX() - mScreenWidth*0.5f)/mScreenMin;
441
      float y = (mScreenHeight*0.5f -event.getY())/mScreenMin;
442

    
443
      switch(action)
444
         {
445
         case MotionEvent.ACTION_DOWN: mX = x;
446
                                       mY = y;
447

    
448
                                       Static4D touchPoint1 = new Static4D(x, y, 0, 0);
449
                                       Static4D rotatedTouchPoint1= rotateVectorByInvertedQuat(touchPoint1, mQuatAccumulated);
450
                                       Static4D rotatedCamera= rotateVectorByInvertedQuat(CAMERA_POINT, mQuatAccumulated);
451

    
452
                                       if( mMovement.faceTouched(rotatedTouchPoint1, rotatedCamera) )
453
                                         {
454
                                         mDragging           = false;
455
                                         mBeginningRotation  = mRenderer.canRotate();
456
                                         mContinuingRotation = false;
457
                                         }
458
                                       else
459
                                         {
460
                                         mDragging           = mRenderer.canDrag();
461
                                         mBeginningRotation  = false;
462
                                         mContinuingRotation = false;
463
                                         }
464
                                       break;
465
         case MotionEvent.ACTION_MOVE: if( mDragging )
466
                                         {
467
                                         mTempCurrent.set(quatFromDrag(mX-x,y-mY));
468
                                         mRenderer.setQuatCurrentOnNextRender();
469

    
470
                                         if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > 1.0f/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
471
                                           {
472
                                           mX = x;
473
                                           mY = y;
474
                                           mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
475
                                           mTempCurrent.set(0f, 0f, 0f, 1f);
476
                                           mRenderer.setQuatCurrentOnNextRender();
477
                                           mRenderer.setQuatAccumulatedOnNextRender();
478
                                           }
479
                                         }
480
                                       if( mBeginningRotation )
481
                                         {
482
                                         if( (mX-x)*(mX-x)+(mY-y)*(mY-y) > 1.0f/(ROTATION_SENSITIVITY*ROTATION_SENSITIVITY) )
483
                                           {
484
                                           Static4D touchPoint2 = new Static4D(x, y, 0, 0);
485
                                           Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuatAccumulated);
486

    
487
                                           Static2D rot = mMovement.newRotation(rotatedTouchPoint2);
488
                                           RubikCube cube = mRenderer.getCube();
489

    
490
                                           cube.addNewRotation( (int)rot.get0(), (int)(cube.getSize()*rot.get1()) );
491

    
492
                                           mBeginningRotation = false;
493
                                           mContinuingRotation= true;
494
                                           }
495
                                         }
496
                                       else if( mContinuingRotation )
497
                                         {
498
                                         Static4D touchPoint3 = new Static4D(x, y, 0, 0);
499
                                         Static4D rotatedTouchPoint3= rotateVectorByInvertedQuat(touchPoint3, mQuatAccumulated);
500

    
501
                                         float angle = mMovement.continueRotation(rotatedTouchPoint3);
502
                                         mRenderer.getCube().continueRotation(SWIPING_SENSITIVITY*angle);
503
                                         }
504
                                       break;
505
         case MotionEvent.ACTION_UP  : if( mDragging )
506
                                         {
507
                                         mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
508
                                         mTempCurrent.set(0f, 0f, 0f, 1f);
509
                                         mRenderer.setQuatCurrentOnNextRender();
510
                                         mRenderer.setQuatAccumulatedOnNextRender();
511
                                         }
512

    
513
                                       if( mContinuingRotation )
514
                                         {
515
                                         mRenderer.finishRotation();
516
                                         }
517
                                       break;
518
         }
519

    
520
      return true;
521
      }
522
}
523

    
(12-12/12)