Project

General

Profile

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

magiccube / src / main / java / org / distorted / tutorials / TutorialSurfaceView.java @ 4946b635

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2020 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.tutorials;
21

    
22
import android.app.ActivityManager;
23
import android.content.Context;
24
import android.content.pm.ConfigurationInfo;
25
import android.opengl.GLES30;
26
import android.opengl.GLSurfaceView;
27
import android.util.AttributeSet;
28
import android.util.DisplayMetrics;
29
import android.view.MotionEvent;
30

    
31
import com.google.firebase.crashlytics.FirebaseCrashlytics;
32

    
33
import org.distorted.helpers.QuatHelper;
34
import org.distorted.library.type.Static2D;
35
import org.distorted.library.type.Static4D;
36
import org.distorted.objects.Movement;
37
import org.distorted.objects.TwistyObject;
38

    
39
import static org.distorted.main.RubikSurfaceView.INVALID_POINTER_ID;
40
import static org.distorted.main.RubikSurfaceView.NUM_SPEED_PROBES;
41
import static org.distorted.main.RubikSurfaceView.ROTATION_SENSITIVITY;
42
import static org.distorted.main.RubikSurfaceView.SWIPING_SENSITIVITY;
43

    
44
///////////////////////////////////////////////////////////////////////////////////////////////////
45

    
46
public class TutorialSurfaceView extends GLSurfaceView
47
{
48
    private final Static4D CAMERA_POINT = new Static4D(0, 0, 0, 0);
49
    private TutorialRenderer mRenderer;
50
    private TutorialPreRender mPreRender;
51
    private Movement mMovement;
52
    private boolean mDragging, mBeginningRotation, mContinuingRotation;
53
    private int mScreenWidth, mScreenHeight, mScreenMin;
54

    
55
    private float mRotAngle, mInitDistance;
56
    private int mPtrID1, mPtrID2;
57
    private float mX, mY;
58
    private float mStartRotX, mStartRotY;
59
    private float mAxisX, mAxisY;
60
    private float mRotationFactor;
61
    private int mCurrentAxis, mCurrentRow;
62
    private float mCurrentAngle, mCurrRotSpeed;
63
    private float[] mLastX;
64
    private float[] mLastY;
65
    private long[] mLastT;
66
    private int mFirstIndex, mLastIndex;
67
    private int mDensity;
68

    
69
    private static final Static4D mQuat= new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
70
    private static final Static4D mTemp= new Static4D(0,0,0,1);
71

    
72
///////////////////////////////////////////////////////////////////////////////////////////////////
73

    
74
    void setScreenSize(int width, int height)
75
      {
76
      mScreenWidth = width;
77
      mScreenHeight= height;
78

    
79
      mScreenMin = Math.min(width, height);
80
      }
81

    
82
///////////////////////////////////////////////////////////////////////////////////////////////////
83

    
84
    boolean isVertical()
85
      {
86
      return mScreenHeight>mScreenWidth;
87
      }
88

    
89
///////////////////////////////////////////////////////////////////////////////////////////////////
90

    
91
    TutorialRenderer getRenderer()
92
      {
93
      return mRenderer;
94
      }
95

    
96
///////////////////////////////////////////////////////////////////////////////////////////////////
97

    
98
    TutorialPreRender getPreRender()
99
      {
100
      return mPreRender;
101
      }
102

    
103
///////////////////////////////////////////////////////////////////////////////////////////////////
104

    
105
    void setQuat()
106
      {
107
      mQuat.set(mTemp);
108
      }
109

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

    
112
    Static4D getQuat()
113
      {
114
      return mQuat;
115
      }
116

    
117
///////////////////////////////////////////////////////////////////////////////////////////////////
118

    
119
    void setMovement(Movement movement)
120
      {
121
      mMovement = movement;
122
      }
123

    
124
///////////////////////////////////////////////////////////////////////////////////////////////////
125
// cast the 3D axis we are currently rotating along (which is already casted to the surface of the
126
// currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
127

    
128
    private void computeCurrentAxis(Static4D axis)
129
      {
130
      Static4D result = QuatHelper.rotateVectorByQuat(axis, mQuat);
131

    
132
      mAxisX =result.get0();
133
      mAxisY =result.get1();
134

    
135
      float len = (float)Math.sqrt(mAxisX*mAxisX + mAxisY*mAxisY);
136
      mAxisX /= len;
137
      mAxisY /= len;
138
      }
139

    
140
///////////////////////////////////////////////////////////////////////////////////////////////////
141

    
142
    private void addSpeedProbe(float x, float y)
143
      {
144
      long currTime = System.currentTimeMillis();
145
      boolean theSame = mLastIndex==mFirstIndex;
146

    
147
      mLastIndex++;
148
      if( mLastIndex>=NUM_SPEED_PROBES ) mLastIndex=0;
149

    
150
      mLastT[mLastIndex] = currTime;
151
      mLastX[mLastIndex] = x;
152
      mLastY[mLastIndex] = y;
153

    
154
      if( mLastIndex==mFirstIndex)
155
        {
156
        mFirstIndex++;
157
        if( mFirstIndex>=NUM_SPEED_PROBES ) mFirstIndex=0;
158
        }
159

    
160
      if( theSame )
161
        {
162
        mLastT[mFirstIndex] = currTime;
163
        mLastX[mFirstIndex] = x;
164
        mLastY[mFirstIndex] = y;
165
        }
166
      }
167

    
168
///////////////////////////////////////////////////////////////////////////////////////////////////
169

    
170
    private void computeCurrentSpeedInInchesPerSecond()
171
      {
172
      long firstTime = mLastT[mFirstIndex];
173
      long lastTime  = mLastT[mLastIndex];
174
      float fX = mLastX[mFirstIndex];
175
      float fY = mLastY[mFirstIndex];
176
      float lX = mLastX[mLastIndex];
177
      float lY = mLastY[mLastIndex];
178

    
179
      long timeDiff = lastTime-firstTime;
180

    
181
      mLastIndex = 0;
182
      mFirstIndex= 0;
183

    
184
      mCurrRotSpeed = timeDiff>0 ? 1000*retFingerDragDistanceInInches(fX,fY,lX,lY)/timeDiff : 0;
185
      }
186

    
187
///////////////////////////////////////////////////////////////////////////////////////////////////
188

    
189
    private float retFingerDragDistanceInInches(float xFrom, float yFrom, float xTo, float yTo)
190
      {
191
      float xDist = mScreenWidth*(xFrom-xTo);
192
      float yDist = mScreenHeight*(yFrom-yTo);
193
      float distInPixels = (float)Math.sqrt(xDist*xDist + yDist*yDist);
194

    
195
      return distInPixels/mDensity;
196
      }
197

    
198
///////////////////////////////////////////////////////////////////////////////////////////////////
199

    
200
    private void setUpDragOrRotate(float x, float y)
201
      {
202
      TwistyObject object = mPreRender.getObject();
203
      CAMERA_POINT.set2( object==null ? 1.21f : object.getCameraDist() );
204

    
205
      Static4D touchPoint = new Static4D(x, y, 0, 0);
206
      Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, mQuat);
207
      Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
208

    
209
      if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera,object.getObjectRatio()) )
210
        {
211
        mDragging           = false;
212
        mContinuingRotation = false;
213
        mBeginningRotation= !mPreRender.isTouchBlocked();
214
        }
215
      else
216
        {
217
        final TutorialActivity act = (TutorialActivity)getContext();
218
        boolean locked      = act.isLocked();
219
        mDragging           = !locked;
220
        mContinuingRotation = false;
221
        mBeginningRotation  = false;
222

    
223
        if( !mDragging )
224
          {
225
          TutorialState state = act.getState();
226
          state.reddenLock(act);
227
          }
228
        }
229
      }
230

    
231
///////////////////////////////////////////////////////////////////////////////////////////////////
232

    
233
    private void drag(MotionEvent event, float x, float y)
234
      {
235
      if( mPtrID1!=INVALID_POINTER_ID && mPtrID2!=INVALID_POINTER_ID)
236
        {
237
        int pointer = event.findPointerIndex(mPtrID2);
238
        float pX,pY;
239

    
240
        try
241
          {
242
          pX = event.getX(pointer);
243
          pY = event.getY(pointer);
244
          }
245
        catch(IllegalArgumentException ex)
246
          {
247
          mPtrID1=INVALID_POINTER_ID;
248
          mPtrID2=INVALID_POINTER_ID;
249

    
250
          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
251
          crashlytics.setCustomKey("DragError", "pointer="+pointer );
252
          crashlytics.recordException(ex);
253

    
254
          return;
255
          }
256

    
257
        float x2 = (pX - mScreenWidth*0.5f)/mScreenMin;
258
        float y2 = (mScreenHeight*0.5f -pY)/mScreenMin;
259

    
260
        float angleNow = getAngle(x,y,x2,y2);
261
        float angleDiff = angleNow-mRotAngle;
262
        float sinA =-(float)Math.sin(angleDiff);
263
        float cosA = (float)Math.cos(angleDiff);
264

    
265
        Static4D dragQuat = QuatHelper.quatMultiply(new Static4D(0,0,sinA,cosA), mQuat);
266
        mTemp.set(dragQuat);
267

    
268
        mRotAngle = angleNow;
269

    
270
        float distNow  = (float)Math.sqrt( (x-x2)*(x-x2) + (y-y2)*(y-y2) );
271
        float distQuot = mInitDistance<0 ? 1.0f : distNow/ mInitDistance;
272
        mInitDistance = distNow;
273

    
274
        TwistyObject object = mPreRender.getObject();
275
        if( object!=null ) object.setObjectRatio(distQuot);
276
        }
277
      else
278
        {
279
        Static4D dragQuat = QuatHelper.quatMultiply(QuatHelper.quatFromDrag(mX-x,y-mY), mQuat);
280
        mTemp.set(dragQuat);
281
        }
282

    
283
      mPreRender.setQuatOnNextRender();
284
      mX = x;
285
      mY = y;
286
      }
287

    
288
///////////////////////////////////////////////////////////////////////////////////////////////////
289

    
290
    private void finishRotation()
291
      {
292
      computeCurrentSpeedInInchesPerSecond();
293
      int angle = mPreRender.getObject().computeNearestAngle(mCurrentAxis,mCurrentAngle, mCurrRotSpeed);
294
      mPreRender.finishRotation(angle);
295

    
296
      if( angle!=0 )
297
        {
298
        final TutorialActivity act = (TutorialActivity)getContext();
299
        TutorialState state = act.getState();
300
        state.addMove(act,mCurrentAxis, mCurrentRow, angle);
301
        }
302

    
303
      mContinuingRotation = false;
304
      mBeginningRotation  = false;
305
      mDragging           = true;
306
      }
307

    
308
///////////////////////////////////////////////////////////////////////////////////////////////////
309

    
310
    private void continueRotation(float x, float y)
311
      {
312
      float dx = x-mStartRotX;
313
      float dy = y-mStartRotY;
314
      float alpha = dx*mAxisX + dy*mAxisY;
315
      float x2 = dx - alpha*mAxisX;
316
      float y2 = dy - alpha*mAxisY;
317

    
318
      float len = (float)Math.sqrt(x2*x2 + y2*y2);
319

    
320
      // we have the length of 1D vector 'angle', now the direction:
321
      float tmp = mAxisY==0 ? -mAxisX*y2 : mAxisY*x2;
322

    
323
      float angle = (tmp>0 ? 1:-1)*len*mRotationFactor;
324
      mCurrentAngle = SWIPING_SENSITIVITY*angle;
325
      mPreRender.getObject().continueRotation(mCurrentAngle);
326

    
327
      addSpeedProbe(x2,y2);
328
      }
329

    
330
///////////////////////////////////////////////////////////////////////////////////////////////////
331

    
332
    private void beginRotation(float x, float y)
333
      {
334
      mStartRotX = x;
335
      mStartRotY = y;
336

    
337
      TwistyObject object = mPreRender.getObject();
338
      int numLayers = object.getNumLayers();
339

    
340
      Static4D touchPoint2 = new Static4D(x, y, 0, 0);
341
      Static4D rotatedTouchPoint2= QuatHelper.rotateVectorByInvertedQuat(touchPoint2, mQuat);
342
      Static2D res = mMovement.newRotation(rotatedTouchPoint2,object.getObjectRatio());
343

    
344
      mCurrentAxis = (int)res.get0();
345
      mCurrentRow  = (int)res.get1();
346

    
347
      computeCurrentAxis( mMovement.getCastedRotAxis(mCurrentAxis) );
348
      mRotationFactor = mMovement.returnRotationFactor(numLayers,mCurrentRow);
349

    
350
      object.beginNewRotation( mCurrentAxis, mCurrentRow );
351

    
352
      addSpeedProbe(x,y);
353

    
354
      mBeginningRotation = false;
355
      mContinuingRotation= true;
356
      }
357

    
358
///////////////////////////////////////////////////////////////////////////////////////////////////
359

    
360
    private float getAngle(float x1, float y1, float x2, float y2)
361
      {
362
      return (float) Math.atan2(y1-y2, x1-x2);
363
      }
364

    
365
///////////////////////////////////////////////////////////////////////////////////////////////////
366

    
367
    private void actionMove(MotionEvent event)
368
      {
369
      int pointer = event.findPointerIndex(mPtrID1 != INVALID_POINTER_ID ? mPtrID1:mPtrID2);
370

    
371
      if( pointer<0 ) return;
372

    
373
      float pX = event.getX(pointer);
374
      float pY = event.getY(pointer);
375

    
376
      float x = (pX - mScreenWidth*0.5f)/mScreenMin;
377
      float y = (mScreenHeight*0.5f -pY)/mScreenMin;
378

    
379
      if( mBeginningRotation )
380
        {
381
        if( retFingerDragDistanceInInches(mX,mY,x,y) > ROTATION_SENSITIVITY )
382
          {
383
          beginRotation(x,y);
384
          }
385
        }
386
      else if( mContinuingRotation )
387
        {
388
        continueRotation(x,y);
389
        }
390
      else if( mDragging )
391
        {
392
        drag(event,x,y);
393
        }
394
      else
395
        {
396
        setUpDragOrRotate(x,y);
397
        }
398
      }
399

    
400
///////////////////////////////////////////////////////////////////////////////////////////////////
401

    
402
    private void actionDown(MotionEvent event)
403
      {
404
      mPtrID1 = event.getPointerId(0);
405

    
406
      float x = event.getX();
407
      float y = event.getY();
408

    
409
      mX = (x - mScreenWidth*0.5f)/mScreenMin;
410
      mY = (mScreenHeight*0.5f -y)/mScreenMin;
411

    
412
      setUpDragOrRotate(mX,mY);
413
      }
414

    
415
///////////////////////////////////////////////////////////////////////////////////////////////////
416

    
417
    private void actionUp(MotionEvent event)
418
      {
419
      mPtrID1 = INVALID_POINTER_ID;
420
      mPtrID2 = INVALID_POINTER_ID;
421

    
422
      if( mContinuingRotation )
423
        {
424
        finishRotation();
425
        }
426
      }
427

    
428
///////////////////////////////////////////////////////////////////////////////////////////////////
429

    
430
    private void actionDown2(MotionEvent event)
431
      {
432
      int index = event.getActionIndex();
433

    
434
      if( mPtrID1==INVALID_POINTER_ID )
435
        {
436
        mPtrID1 = event.getPointerId(index);
437
        float x = event.getX();
438
        float y = event.getY();
439

    
440
        if( mPtrID2 != INVALID_POINTER_ID )
441
          {
442
          int pointer = event.findPointerIndex(mPtrID2);
443

    
444
          try
445
            {
446
            float x2 = event.getX(pointer);
447
            float y2 = event.getY(pointer);
448

    
449
            mRotAngle = getAngle(x,-y,x2,-y2);
450
            mInitDistance = -1;
451
            }
452
          catch(IllegalArgumentException ex)
453
            {
454
            mPtrID1=INVALID_POINTER_ID;
455
            mPtrID2=INVALID_POINTER_ID;
456

    
457
            FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
458
            crashlytics.setCustomKey("DragError", "pointer="+pointer );
459
            crashlytics.recordException(ex);
460

    
461
            return;
462
            }
463
          }
464

    
465
        mX = (x - mScreenWidth*0.5f)/mScreenMin;
466
        mY = (mScreenHeight*0.5f -y)/mScreenMin;
467
        }
468
      else if( mPtrID2==INVALID_POINTER_ID )
469
        {
470
        mPtrID2 = event.getPointerId(index);
471

    
472
        float x = event.getX();
473
        float y = event.getY();
474

    
475
        if( mPtrID2 != INVALID_POINTER_ID )
476
          {
477
          int pointer = event.findPointerIndex(mPtrID2);
478

    
479
          try
480
            {
481
            float x2 = event.getX(pointer);
482
            float y2 = event.getY(pointer);
483

    
484
            mRotAngle = getAngle(x,-y,x2,-y2);
485
            mInitDistance = -1;
486
            }
487
          catch(IllegalArgumentException ex)
488
            {
489
            mPtrID1=INVALID_POINTER_ID;
490
            mPtrID2=INVALID_POINTER_ID;
491

    
492
            FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
493
            crashlytics.setCustomKey("DragError", "pointer="+pointer );
494
            crashlytics.recordException(ex);
495

    
496
            return;
497
            }
498
          }
499

    
500
        if( mBeginningRotation || mContinuingRotation )
501
          {
502
          mX = (x - mScreenWidth*0.5f)/mScreenMin;
503
          mY = (mScreenHeight*0.5f -y)/mScreenMin;
504
          }
505
        }
506

    
507
      if( mBeginningRotation )
508
        {
509
        mContinuingRotation = false;
510
        mBeginningRotation  = false;
511
        mDragging           = true;
512
        }
513
      else if( mContinuingRotation )
514
        {
515
        finishRotation();
516
        }
517
      }
518

    
519
///////////////////////////////////////////////////////////////////////////////////////////////////
520

    
521
    private void actionUp2(MotionEvent event)
522
      {
523
      int index = event.getActionIndex();
524

    
525
      if( index==event.findPointerIndex(mPtrID1) )
526
        {
527
        mPtrID1 = INVALID_POINTER_ID;
528
        int pointer = event.findPointerIndex(mPtrID2);
529

    
530
        if( pointer>=0 )
531
          {
532
          float x1 = event.getX(pointer);
533
          float y1 = event.getY(pointer);
534

    
535
          mX = (x1 - mScreenWidth*0.5f)/mScreenMin;
536
          mY = (mScreenHeight*0.5f -y1)/mScreenMin;
537
          }
538
        }
539
      else if( index==event.findPointerIndex(mPtrID2) )
540
        {
541
        mPtrID2 = INVALID_POINTER_ID;
542
        }
543
      }
544

    
545
///////////////////////////////////////////////////////////////////////////////////////////////////
546

    
547
    void initialize()
548
      {
549
      mPtrID1 = INVALID_POINTER_ID;
550
      mPtrID2 = INVALID_POINTER_ID;
551
      }
552

    
553
///////////////////////////////////////////////////////////////////////////////////////////////////
554
// PUBLIC API
555
///////////////////////////////////////////////////////////////////////////////////////////////////
556

    
557
    public TutorialSurfaceView(Context context, AttributeSet attrs)
558
      {
559
      super(context,attrs);
560

    
561
      if(!isInEditMode())
562
        {
563
        mCurrRotSpeed= 0.0f;
564

    
565
        mLastX = new float[NUM_SPEED_PROBES];
566
        mLastY = new float[NUM_SPEED_PROBES];
567
        mLastT = new long[NUM_SPEED_PROBES];
568
        mFirstIndex =0;
569
        mLastIndex  =0;
570

    
571
        mRenderer  = new TutorialRenderer(this);
572
        mPreRender = new TutorialPreRender(this);
573

    
574
        TutorialActivity act = (TutorialActivity)context;
575
        DisplayMetrics dm = new DisplayMetrics();
576
        act.getWindowManager().getDefaultDisplay().getMetrics(dm);
577

    
578
        mDensity = dm.densityDpi;
579

    
580
        final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
581

    
582
        try
583
          {
584
          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
585
          int esVersion = configurationInfo.reqGlEsVersion>>16;
586
          setEGLContextClientVersion(esVersion);
587
          setRenderer(mRenderer);
588
          }
589
        catch(Exception ex)
590
          {
591
          act.OpenGLError();
592

    
593
          String shading = GLES30.glGetString(GLES30.GL_SHADING_LANGUAGE_VERSION);
594
          String version = GLES30.glGetString(GLES30.GL_VERSION);
595
          String vendor  = GLES30.glGetString(GLES30.GL_VENDOR);
596
          String renderer= GLES30.glGetString(GLES30.GL_RENDERER);
597

    
598
          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
599
          crashlytics.setCustomKey("GLSL Version"  , shading );
600
          crashlytics.setCustomKey("GLversion"     , version );
601
          crashlytics.setCustomKey("GL Vendor "    , vendor  );
602
          crashlytics.setCustomKey("GLSLrenderer"  , renderer);
603
          crashlytics.recordException(ex);
604
          }
605
        }
606
      }
607

    
608
///////////////////////////////////////////////////////////////////////////////////////////////////
609

    
610
    @Override
611
    public boolean onTouchEvent(MotionEvent event)
612
      {
613
      int action = event.getActionMasked();
614

    
615
      switch(action)
616
         {
617
         case MotionEvent.ACTION_DOWN        : actionDown(event) ; break;
618
         case MotionEvent.ACTION_MOVE        : actionMove(event) ; break;
619
         case MotionEvent.ACTION_UP          : actionUp(event)   ; break;
620
         case MotionEvent.ACTION_POINTER_DOWN: actionDown2(event); break;
621
         case MotionEvent.ACTION_POINTER_UP  : actionUp2(event)  ; break;
622
         }
623

    
624
      return true;
625
      }
626
}
627

    
(6-6/7)