Project

General

Profile

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

magiccube / src / main / java / org / distorted / magic / RubikSurfaceView.java @ 4235de9b

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.preference.PreferenceManager;
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.effect.BaseEffect;
43
import org.distorted.library.type.Static2D;
44
import org.distorted.library.type.Static4D;
45
import org.distorted.object.RubikCube;
46
import org.distorted.object.RubikCubeMovement;
47

    
48
///////////////////////////////////////////////////////////////////////////////////////////////////
49

    
50
public class RubikSurfaceView extends GLSurfaceView
51
{
52
    public static final int BUTTON_ID_BACK= 1023;
53
    public static final int BUTTON_ID_EXIT= 1022;
54

    
55
    public static final int MIN_SCRAMBLE =  1;
56
    public static final int DEF_SCRAMBLE =  1;
57
    public static final int MAX_SCRAMBLE = 18;
58

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

    
67
    private final Static4D CAMERA_POINT = new Static4D(0, 0, RubikRenderer.CAMERA_DISTANCE, 0);
68

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

    
75
    private HorizontalNumberPicker mPicker;
76
    private int mScrambleValue;
77

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

    
84
///////////////////////////////////////////////////////////////////////////////////////////////////
85

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

    
92
///////////////////////////////////////////////////////////////////////////////////////////////////
93

    
94
    void savePreferences()
95
      {
96
      RubikActivity act = (RubikActivity)getContext();
97
      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(act);
98
      SharedPreferences.Editor editor = preferences.edit();
99

    
100
      for (int i = 0; i< BaseEffect.Type.LENGTH; i++)
101
        {
102
        BaseEffect.Type.getType(i).savePreferences(editor);
103
        }
104

    
105
      editor.putInt("scramble", mScrambleValue );
106

    
107
      editor.apply();
108
      }
109

    
110
///////////////////////////////////////////////////////////////////////////////////////////////////
111

    
112
    void restorePreferences()
113
      {
114
      RubikActivity act = (RubikActivity)getContext();
115
      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(act);
116

    
117
      for (int i=0; i< BaseEffect.Type.LENGTH; i++)
118
        {
119
        BaseEffect.Type.getType(i).restorePreferences(preferences);
120
        }
121

    
122
      mScrambleValue= preferences.getInt("scramble", DEF_SCRAMBLE);
123
      }
124

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

    
127
    void setScreenSize(int width, int height)
128
      {
129
      mScreenWidth = width;
130
      mScreenHeight= height;
131

    
132
      mScreenMin = width<height ? width:height;
133
      }
134

    
135
///////////////////////////////////////////////////////////////////////////////////////////////////
136

    
137
    RubikRenderer getRenderer()
138
      {
139
      return mRenderer;
140
      }
141

    
142
///////////////////////////////////////////////////////////////////////////////////////////////////
143

    
144
    static int getRedButton()
145
      {
146
      return mButton;
147
      }
148

    
149
///////////////////////////////////////////////////////////////////////////////////////////////////
150

    
151
    void setQuatAccumulated()
152
      {
153
      mQuatAccumulated.set(mTempAccumulated);
154
      }
155

    
156
///////////////////////////////////////////////////////////////////////////////////////////////////
157

    
158
    void setQuatCurrent()
159
      {
160
      mQuatCurrent.set(mTempCurrent);
161
      }
162

    
163
///////////////////////////////////////////////////////////////////////////////////////////////////
164

    
165
    Static4D getQuatAccumulated()
166
      {
167
      return mQuatAccumulated;
168
      }
169

    
170
///////////////////////////////////////////////////////////////////////////////////////////////////
171

    
172
    Static4D getQuatCurrent()
173
      {
174
      return mQuatCurrent;
175
      }
176

    
177
///////////////////////////////////////////////////////////////////////////////////////////////////
178

    
179
    private Static4D quatFromDrag(float dragX, float dragY)
180
      {
181
      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
182
      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
183
      float axisZ = 0;
184
      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
185

    
186
      if( axisL>0 )
187
        {
188
        axisX /= axisL;
189
        axisY /= axisL;
190
        axisZ /= axisL;
191

    
192
        float ratio = axisL;
193
        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
194

    
195
        float cosA = (float)Math.cos(Math.PI*ratio);
196
        float sinA = (float)Math.sqrt(1-cosA*cosA);
197

    
198
        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
199
        }
200

    
201
      return new Static4D(0f, 0f, 0f, 1f);
202
      }
203

    
204
///////////////////////////////////////////////////////////////////////////////////////////////////
205
// return quat1*quat2
206

    
207
    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
208
      {
209
      float qx = quat1.get0();
210
      float qy = quat1.get1();
211
      float qz = quat1.get2();
212
      float qw = quat1.get3();
213

    
214
      float rx = quat2.get0();
215
      float ry = quat2.get1();
216
      float rz = quat2.get2();
217
      float rw = quat2.get3();
218

    
219
      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
220
      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
221
      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
222
      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
223

    
224
      return new Static4D(tx,ty,tz,tw);
225
      }
226

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

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

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

    
240
      return quatMultiply(tmp,quatInverted);
241
      }
242

    
243
///////////////////////////////////////////////////////////////////////////////////////////////////
244
// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
245

    
246
    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
247
      {
248
      float qx = quat.get0();
249
      float qy = quat.get1();
250
      float qz = quat.get2();
251
      float qw = quat.get3();
252

    
253
      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
254
      Static4D tmp = quatMultiply(quatInverted,vector);
255

    
256
      return quatMultiply(tmp,quat);
257
      }
258

    
259
///////////////////////////////////////////////////////////////////////////////////////////////////
260

    
261
    void markButton(int button)
262
      {
263
      mButton = button;
264
      RubikActivity act = (RubikActivity)getContext();
265

    
266
      for(int b=0; b<RubikSize.LENGTH; b++)
267
        {
268
        Drawable d = act.findViewById(b).getBackground();
269

    
270
        if( b==button )
271
          {
272
          d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
273
          }
274
        else
275
          {
276
          d.clearColorFilter();
277
          }
278
        }
279
      }
280

    
281
///////////////////////////////////////////////////////////////////////////////////////////////////
282

    
283
    void enterMainState(RubikActivity act)
284
      {
285
      LayoutInflater inflater = act.getLayoutInflater();
286

    
287
      // TOP ////////////////////////////
288
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
289
      layoutTop.removeAllViews();
290
      final View viewTop = inflater.inflate(R.layout.main_title, null);
291
      layoutTop.addView(viewTop);
292

    
293
      // BOT ////////////////////////////
294
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
295
      layoutBot.removeAllViews();
296

    
297
      DisplayMetrics metrics = getResources().getDisplayMetrics();
298
      float scale = metrics.density;
299
      int size = (int)(60*scale +0.5f);
300
      int padding = (int)(5*scale + 0.5f);
301
      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,size,0.5f);
302

    
303
      Button buttonL = new Button(act);
304
      buttonL.setLayoutParams(params);
305
      buttonL.setId(BUTTON_ID_EXIT);
306
      buttonL.setPadding(padding,0,padding,0);
307
      buttonL.setText(R.string.back);
308
      buttonL.setOnClickListener(act);
309
      layoutBot.addView(buttonL);
310

    
311
      Button buttonR = new Button(act);
312
      buttonR.setLayoutParams(params);
313
      buttonR.setId(BUTTON_ID_EXIT);
314
      buttonR.setPadding(padding,0,padding,0);
315
      buttonR.setText(R.string.exit);
316
      buttonR.setOnClickListener(act);
317
      layoutBot.addView(buttonR);
318

    
319
      buttonL.setVisibility(INVISIBLE);
320
      }
321

    
322
///////////////////////////////////////////////////////////////////////////////////////////////////
323

    
324
    void enterPlayState(RubikActivity act)
325
      {
326
      LayoutInflater inflater = act.getLayoutInflater();
327

    
328
      // TOP ////////////////////////////
329
      LinearLayout layoutTop = act.findViewById(R.id.mainTitle);
330
      layoutTop.removeAllViews();
331

    
332
      final View viewTop = inflater.inflate(R.layout.play_title, null);
333
      layoutTop.addView(viewTop);
334

    
335
      // BOT ////////////////////////////
336
      LinearLayout layoutBot = act.findViewById(R.id.mainBar);
337
      layoutBot.removeAllViews();
338

    
339
      DisplayMetrics metrics = getResources().getDisplayMetrics();
340
      float scale = metrics.density;
341
      int size = (int)(60*scale +0.5f);
342
      int padding = (int)(3*scale + 0.5f);
343
      ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
344

    
345
      for(int i=0; i<RubikSize.LENGTH; i++)
346
        {
347
        ImageButton button = new ImageButton(act);
348
        button.setLayoutParams(params);
349
        button.setId(i);
350
        button.setPadding(padding,0,padding,0);
351
        int iconID = RubikSize.getSize(i).getIconID();
352
        button.setImageResource(iconID);
353
        button.setOnClickListener(act);
354
        layoutBot.addView(button);
355
        }
356

    
357
      ViewGroup.LayoutParams params2 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,size);
358

    
359
      Button button = new Button(act);
360
      button.setLayoutParams(params2);
361
      button.setId(BUTTON_ID_BACK);
362
      button.setPadding(padding,0,padding,0);
363
      button.setText(R.string.back);
364
      button.setOnClickListener(act);
365
      layoutBot.addView(button);
366

    
367
      markButton(mButton);
368

    
369
      mPicker = act.findViewById(R.id.rubikNumberPicker);
370

    
371
      if( mPicker!=null )
372
        {
373
        mPicker.setMin(MIN_SCRAMBLE);
374
        mPicker.setMax(MAX_SCRAMBLE);
375
        mPicker.setValue(mScrambleValue);
376
        }
377
      }
378

    
379
///////////////////////////////////////////////////////////////////////////////////////////////////
380
// PUBLIC API
381
///////////////////////////////////////////////////////////////////////////////////////////////////
382

    
383
    public RubikSurfaceView(Context context, AttributeSet attrs)
384
      {
385
      super(context,attrs);
386

    
387
      if(!isInEditMode())
388
        {
389
        mRenderer = new RubikRenderer(this);
390
        mMovement = new RubikCubeMovement();
391

    
392
        final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
393
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
394
        setEGLContextClientVersion( (configurationInfo.reqGlEsVersion>>16) >= 3 ? 3:2 );
395
        setRenderer(mRenderer);
396
        }
397
      }
398

    
399
///////////////////////////////////////////////////////////////////////////////////////////////////
400

    
401
    @Override
402
    public boolean onTouchEvent(MotionEvent event)
403
      {
404
      int action = event.getAction();
405
      float x = (event.getX() - mScreenWidth*0.5f)/mScreenMin;
406
      float y = (mScreenHeight*0.5f -event.getY())/mScreenMin;
407

    
408
      switch(action)
409
         {
410
         case MotionEvent.ACTION_DOWN: mX = x;
411
                                       mY = y;
412

    
413
                                       Static4D touchPoint1 = new Static4D(x, y, 0, 0);
414
                                       Static4D rotatedTouchPoint1= rotateVectorByInvertedQuat(touchPoint1, mQuatAccumulated);
415
                                       Static4D rotatedCamera= rotateVectorByInvertedQuat(CAMERA_POINT, mQuatAccumulated);
416

    
417
                                       if( mMovement.faceTouched(rotatedTouchPoint1, rotatedCamera) )
418
                                         {
419
                                         mDragging           = false;
420
                                         mBeginningRotation  = mRenderer.canRotate();
421
                                         mContinuingRotation = false;
422
                                         }
423
                                       else
424
                                         {
425
                                         mDragging           = mRenderer.canDrag();
426
                                         mBeginningRotation  = false;
427
                                         mContinuingRotation = false;
428
                                         }
429
                                       break;
430
         case MotionEvent.ACTION_MOVE: if( mDragging )
431
                                         {
432
                                         mTempCurrent.set(quatFromDrag(mX-x,y-mY));
433
                                         mRenderer.setQuatCurrentOnNextRender();
434

    
435
                                         if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > 1.0f/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
436
                                           {
437
                                           mX = x;
438
                                           mY = y;
439
                                           mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
440
                                           mTempCurrent.set(0f, 0f, 0f, 1f);
441
                                           mRenderer.setQuatCurrentOnNextRender();
442
                                           mRenderer.setQuatAccumulatedOnNextRender();
443
                                           }
444
                                         }
445
                                       if( mBeginningRotation )
446
                                         {
447
                                         if( (mX-x)*(mX-x)+(mY-y)*(mY-y) > 1.0f/(ROTATION_SENSITIVITY*ROTATION_SENSITIVITY) )
448
                                           {
449
                                           Static4D touchPoint2 = new Static4D(x, y, 0, 0);
450
                                           Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuatAccumulated);
451

    
452
                                           Static2D rot = mMovement.newRotation(rotatedTouchPoint2);
453
                                           RubikCube cube = mRenderer.getCube();
454

    
455
                                           cube.addNewRotation( (int)rot.get0(), (int)(cube.getSize()*rot.get1()) );
456

    
457
                                           mBeginningRotation = false;
458
                                           mContinuingRotation= true;
459
                                           }
460
                                         }
461
                                       else if( mContinuingRotation )
462
                                         {
463
                                         Static4D touchPoint3 = new Static4D(x, y, 0, 0);
464
                                         Static4D rotatedTouchPoint3= rotateVectorByInvertedQuat(touchPoint3, mQuatAccumulated);
465

    
466
                                         float angle = mMovement.continueRotation(rotatedTouchPoint3);
467
                                         mRenderer.getCube().continueRotation(SWIPING_SENSITIVITY*angle);
468
                                         }
469
                                       break;
470
         case MotionEvent.ACTION_UP  : if( mDragging )
471
                                         {
472
                                         mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
473
                                         mTempCurrent.set(0f, 0f, 0f, 1f);
474
                                         mRenderer.setQuatCurrentOnNextRender();
475
                                         mRenderer.setQuatAccumulatedOnNextRender();
476
                                         }
477

    
478
                                       if( mContinuingRotation )
479
                                         {
480
                                         mRenderer.finishRotation();
481
                                         }
482
                                       break;
483
         }
484

    
485
      return true;
486
      }
487
}
488

    
(11-11/11)