commit b9d4aa3b68c8a20335430fb078760a10c4235d99
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Jul 8 13:58:42 2021 +0200

    Lots of changes :)

diff --git a/src/main/java/org/distorted/control/RubikControl.java b/src/main/java/org/distorted/control/RubikControl.java
index c91d419e..768c4f27 100644
--- a/src/main/java/org/distorted/control/RubikControl.java
+++ b/src/main/java/org/distorted/control/RubikControl.java
@@ -25,6 +25,7 @@ import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.message.EffectListener;
 import org.distorted.main.RubikActivity;
 import org.distorted.main.RubikSurfaceView;
+import org.distorted.objects.TwistyObject;
 
 import java.lang.ref.WeakReference;
 
@@ -48,6 +49,14 @@ public class RubikControl implements EffectListener
     return act!=null ? act.getScreen() : null;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TwistyObject getObject()
+    {
+    RubikActivity act = mRefAct.get();
+    return act!=null ? act.getObject() : null;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   RubikSurfaceView getSurfaceView()
@@ -87,11 +96,19 @@ public class RubikControl implements EffectListener
   private void addRotateObjects()
     {
     DistortedScreen screen = getScreen();
-    DistortedNode[] nodes = mRotate.getNodes();
+    DistortedNode[] screenNodes = mRotate.getScreenNodes();
 
-    if( screen!=null && nodes!=null )
+    if( screen!=null && screenNodes!=null )
       {
-      for (DistortedNode node : nodes) screen.attach(node);
+      for (DistortedNode node : screenNodes) screen.attach(node);
+      }
+
+    DistortedNode object = getObject();
+    DistortedNode[] objectNodes = mRotate.getObjectNodes();
+
+    if( object!=null && objectNodes!=null )
+      {
+      for (DistortedNode node : objectNodes) object.attach(node);
       }
     }
 
@@ -100,11 +117,19 @@ public class RubikControl implements EffectListener
   private void removeRotateObjects()
     {
     DistortedScreen screen = getScreen();
-    DistortedNode[] nodes = mRotate.returnNodes();
+    DistortedNode[] screenNodes = mRotate.returnScreenNodes();
 
-    if( screen!=null && nodes!=null )
+    if( screen!=null && screenNodes!=null )
       {
-      for (DistortedNode node : nodes) screen.detach(node);
+      for (DistortedNode node : screenNodes) screen.detach(node);
+      }
+
+    DistortedNode object = getObject();
+    DistortedNode[] objectNodes = mRotate.returnObjectNodes();
+
+    if( object!=null && objectNodes!=null )
+      {
+      for (DistortedNode node : objectNodes) object.detach(node);
       }
     }
 
diff --git a/src/main/java/org/distorted/control/RubikControlRotate.java b/src/main/java/org/distorted/control/RubikControlRotate.java
index 80f6ef70..f162b2e1 100644
--- a/src/main/java/org/distorted/control/RubikControlRotate.java
+++ b/src/main/java/org/distorted/control/RubikControlRotate.java
@@ -19,6 +19,7 @@
 
 package org.distorted.control;
 
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.effect.MatrixEffectScale;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedNode;
@@ -28,92 +29,236 @@ import org.distorted.library.mesh.MeshQuad;
 import org.distorted.library.type.Dynamic;
 import org.distorted.library.type.Dynamic3D;
 import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objects.TwistyObject;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 class RubikControlRotate
   {
-  private static final int NUM_NODE = 1;
+  private static final int NUM_SCREEN = 0;
+  private static final int NUM_OBJECT = 1;
+
+  private static Static4D INIT_QUAT;
 
   private final RubikControl mControl;
-  private DistortedEffects[] mEffects;
-  private DistortedNode[] mNodes;
-  private long mEffectID;
+  private DistortedEffects[] mScreenEffects, mObjectEffects;
+  private DistortedNode[] mScreenNodes, mObjectNodes;
+  private long mScreenEffectID, mObjectEffectID;
+  private Static4D mObjRotQuat;
 
   private Dynamic3D mDynamic;
   private MatrixEffectScale mScale;
-  private MeshQuad mQuad;
+  private MeshQuad mScreenQuad, mObjectQuad;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void createEffects()
+  private void computeInitQuat()
     {
-    if( mEffects==null )
-      {
-      mEffects   = new DistortedEffects[NUM_NODE];
-      mEffects[0]= new DistortedEffects();
+    final double alphaZ = Math.PI/8;
+    final double alphaY = Math.PI/16;
+
+    float sinZ = (float)Math.sin(alphaZ);
+    float cosZ = (float)Math.cos(alphaZ);
+    float sinY = (float)Math.sin(alphaY);
+    float cosY = (float)Math.cos(alphaY);
+
+    Static4D qZ = new Static4D(0,0,sinZ,cosZ);
+    Static4D qY = new Static4D(0,sinY,0,cosY);
+
+    INIT_QUAT = QuatHelper.quatMultiply(qY,qZ);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Static4D computeQuat(Static3D ax)
+    {
+    return null;
+    }
 
-      DistortedScreen screen = mControl.getScreen();
-      int wid = screen.getWidth();
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-      Static3D scaleStart= new Static3D(1,1,1);
-      Static3D scaleEnd  = new Static3D(wid,wid,wid);
+  private void computeRotQuat()
+    {
+    TwistyObject object = mControl.getObject();
+    Static3D[] axis = object.getRotationAxis();
+    float cos,maxCos = -1.0f;
+    Static4D quat;
 
-      mDynamic = new Dynamic3D(10000,0.5f);
-      mDynamic.add(scaleStart);
-      mDynamic.add(scaleEnd  );
-      mDynamic.add(scaleStart);
-      mDynamic.setMode(Dynamic.MODE_PATH);
+    for (Static3D axi : axis)
+      {
+      quat = computeQuat(axi);
+      cos = quat.get3();
 
-      mScale = new MatrixEffectScale(mDynamic);
-      mScale.notifyWhenFinished(mControl);
-      mEffectID = mScale.getID();
-      mEffects[0].apply(mScale);
+      if (cos > maxCos)
+        {
+        maxCos = cos;
+        mObjRotQuat = quat;
+        }
       }
-    else
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setScreenEffectsStage1()
+    {
+    mDynamic.resetToBeginning();
+    mScale.notifyWhenFinished(mControl);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setObjectEffectsStage1()
+    {
+    // TODO
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createScreenEffects()
+    {
+    mScreenEffects   = new DistortedEffects[NUM_SCREEN];
+    mScreenEffects[0]= new DistortedEffects();
+
+    DistortedScreen screen = mControl.getScreen();
+    int wid = screen.getWidth();
+
+    Static3D scaleStart= new Static3D(1,1,1);
+    Static3D scaleEnd  = new Static3D(wid,wid,wid);
+
+    mDynamic = new Dynamic3D(10000,0.5f);
+    mDynamic.add(scaleStart);
+    mDynamic.add(scaleEnd  );
+    mDynamic.add(scaleStart);
+    mDynamic.setMode(Dynamic.MODE_PATH);
+
+    mScale = new MatrixEffectScale(mDynamic);
+    mScreenEffectID = mScale.getID();
+    mScreenEffects[0].apply(mScale);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createObjectEffects()
+    {
+    mObjectEffects   = new DistortedEffects[NUM_OBJECT];
+    mObjectEffects[0]= new DistortedEffects();
+
+    DistortedScreen screen = mControl.getScreen();
+    int wid = screen.getWidth();
+
+    Static3D scaleStart= new Static3D(1,1,1);
+    Static3D scaleEnd  = new Static3D(wid,wid,wid);
+
+    mDynamic = new Dynamic3D(10000,0.5f);
+    mDynamic.add(scaleStart);
+    mDynamic.add(scaleEnd  );
+    mDynamic.add(scaleStart);
+    mDynamic.setMode(Dynamic.MODE_PATH);
+
+    mScale = new MatrixEffectScale(mDynamic);
+    mObjectEffectID = mScale.getID();
+    mObjectEffects[0].apply(mScale);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createScreenNodes()
+    {
+    if( mScreenNodes==null )
       {
-      mDynamic.resetToBeginning();
-      mScale.notifyWhenFinished(mControl);
+      mScreenNodes= new DistortedNode[NUM_SCREEN];
+      mScreenQuad = new MeshQuad();
       }
+
+    DistortedTexture texture = new DistortedTexture();
+    texture.setColorARGB(0xff00ff00);
+    mScreenNodes[0] = new DistortedNode(texture, mScreenEffects[0], mScreenQuad);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void createNodes()
+  private void createObjectNodes()
     {
-    if( mNodes==null )
+    if( mObjectNodes==null )
       {
-      mNodes = new DistortedNode[NUM_NODE];
-      mQuad = new MeshQuad();
+      mObjectNodes= new DistortedNode[NUM_OBJECT];
+      mObjectQuad = new MeshQuad();
+      }
+
+    if( INIT_QUAT==null )
+      {
+      computeInitQuat();
+      computeRotQuat();
       }
 
     DistortedTexture texture = new DistortedTexture();
     texture.setColorARGB(0xff00ff00);
-    mNodes[0] = new DistortedNode(texture, mEffects[0], mQuad);
+    mObjectNodes[0] = new DistortedNode(texture, mObjectEffects[0], mObjectQuad);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// ??
 
   long getEffectID()
     {
-    return mEffectID;
+    return mObjectEffectID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long getScreenEffectID()
+    {
+    return mScreenEffectID;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  DistortedNode[] getNodes()
+  long getObjectEffectID()
     {
-    createEffects();
-    createNodes();
+    return mObjectEffectID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode[] getScreenNodes()
+    {
+    if( NUM_SCREEN>0 )
+      {
+      if( mScreenEffects==null ) createScreenEffects();
+      createScreenNodes();
+      setScreenEffectsStage1();
+      }
+
+    return mScreenNodes;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode[] returnScreenNodes()
+    {
+    return mScreenNodes;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   DistortedNode[] getObjectNodes()
+    {
+    if( NUM_OBJECT>0 )
+      {
+      if( mObjectEffects==null ) createObjectEffects();
+      createObjectNodes();
+      setObjectEffectsStage1();
+      }
 
-    return mNodes;
+    return mObjectNodes;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  DistortedNode[] returnNodes()
+   DistortedNode[] returnObjectNodes()
     {
-    return mNodes;
+    return mObjectNodes;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/control/RubikControlWhole.java b/src/main/java/org/distorted/control/RubikControlWhole.java
index 763eed04..05936ffc 100644
--- a/src/main/java/org/distorted/control/RubikControlWhole.java
+++ b/src/main/java/org/distorted/control/RubikControlWhole.java
@@ -50,7 +50,7 @@ class RubikControlWhole
   private static final int D2 =  250; // finger press
   private static final int D3 =10000; // finger triangle
   private static final int D4 = 3000; // fingers approach
-  private static final int D5 =10000; // fingers circle
+  private static final int D5 = 8000; // fingers circle
 
   private static final int[] DUR = { D1, D2, D3, D2, D1/4, 3*D1/4, D1/4, D2, D4, D5, D4, D2, D1 };
   private static final int[] DYN = { 2, 1, 1, 1, 2, 2, 4, 2, 2, 2, 2, 2, 4};
@@ -826,11 +826,12 @@ class RubikControlWhole
       Bitmap bmpHand = openBitmap(act, R.drawable.ui_hand_pointer);
 
       mTextureCirc = new DistortedTexture();
-      mTextureCirc.setTexture(bmpCirc);
       mTextureShad = new DistortedTexture();
-      mTextureShad.setTexture(bmpShad);
       DistortedTexture textureHand = new DistortedTexture();
-      textureHand.setTexture(bmpHand);
+
+      if( bmpCirc!=null ) mTextureCirc.setTexture(bmpCirc);
+      if( bmpShad!=null ) mTextureShad.setTexture(bmpShad);
+      if( bmpHand!=null ) textureHand.setTexture(bmpHand);
 
       mNodes[0]= new DistortedNode(mTextureShad,mEffects[0],mQuad);
       mNodes[1]= new DistortedNode(mTextureShad,mEffects[1],mQuad);
diff --git a/src/main/java/org/distorted/helpers/QuatHelper.java b/src/main/java/org/distorted/helpers/QuatHelper.java
new file mode 100644
index 00000000..d9c5e809
--- /dev/null
+++ b/src/main/java/org/distorted/helpers/QuatHelper.java
@@ -0,0 +1,109 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.helpers;
+
+import org.distorted.library.type.Static4D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class QuatHelper
+  {
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return quat1*quat2
+
+    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
+      {
+      float qx = quat1.get0();
+      float qy = quat1.get1();
+      float qz = quat1.get2();
+      float qw = quat1.get3();
+
+      float rx = quat2.get0();
+      float ry = quat2.get1();
+      float rz = quat2.get2();
+      float rw = quat2.get3();
+
+      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+      return new Static4D(tx,ty,tz,tw);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
+
+    public static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
+      {
+      float qx = quat.get0();
+      float qy = quat.get1();
+      float qz = quat.get2();
+      float qw = quat.get3();
+
+      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
+      Static4D tmp = quatMultiply(quat,vector);
+
+      return quatMultiply(tmp,quatInverted);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
+
+    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
+      {
+      float qx = quat.get0();
+      float qy = quat.get1();
+      float qz = quat.get2();
+      float qw = quat.get3();
+
+      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
+      Static4D tmp = quatMultiply(quatInverted,vector);
+
+      return quatMultiply(tmp,quat);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public static Static4D quatFromDrag(float dragX, float dragY)
+      {
+      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
+      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
+      float axisZ = 0;
+      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
+
+      if( axisL>0 )
+        {
+        axisX /= axisL;
+        axisY /= axisL;
+        axisZ /= axisL;
+
+        float ratio = axisL;
+        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
+
+        float cosA = (float)Math.cos(Math.PI*ratio);
+        float sinA = (float)Math.sqrt(1-cosA*cosA);
+
+        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
+        }
+
+      return new Static4D(0f, 0f, 0f, 1f);
+      }
+  }
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index ca963586..7d520af5 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -30,6 +30,7 @@ import android.view.MotionEvent;
 
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
 import org.distorted.objects.TwistyObject;
@@ -45,8 +46,8 @@ import org.distorted.screens.RubikScreenSolving;
 
 public class RubikSurfaceView extends GLSurfaceView
 {
-    private static final int NUM_SPEED_PROBES = 10;
-    private static final int INVALID_POINTER_ID = -1;
+    public static final int NUM_SPEED_PROBES = 10;
+    public static final int INVALID_POINTER_ID = -1;
 
     public static final int MODE_ROTATE  = 0;
     public static final int MODE_DRAG    = 1;
@@ -54,9 +55,9 @@ public class RubikSurfaceView extends GLSurfaceView
 
     // Moving the finger from the middle of the vertical screen to the right edge will rotate a
     // given face by SWIPING_SENSITIVITY/2 degrees.
-    private final static int SWIPING_SENSITIVITY  = 240;
+    public final static int SWIPING_SENSITIVITY  = 240;
     // Moving the finger by 0.3 of an inch will start a Rotation.
-    private final static float ROTATION_SENSITIVITY = 0.3f;
+    public final static float ROTATION_SENSITIVITY = 0.3f;
 
     private final Static4D CAMERA_POINT = new Static4D(0, 0, 0, 0);
 
@@ -138,40 +139,13 @@ public class RubikSurfaceView extends GLSurfaceView
       mMovement = movement;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private Static4D quatFromDrag(float dragX, float dragY)
-      {
-      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
-      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
-      float axisZ = 0;
-      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
-
-      if( axisL>0 )
-        {
-        axisX /= axisL;
-        axisY /= axisL;
-        axisZ /= axisL;
-
-        float ratio = axisL;
-        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
-
-        float cosA = (float)Math.cos(Math.PI*ratio);
-        float sinA = (float)Math.sqrt(1-cosA*cosA);
-
-        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
-        }
-
-      return new Static4D(0f, 0f, 0f, 1f);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // cast the 3D axis we are currently rotating along (which is already casted to the surface of the
 // currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
 
     private void computeCurrentAxis(Static4D axis)
       {
-      Static4D result = rotateVectorByQuat(axis, mQuat);
+      Static4D result = QuatHelper.rotateVectorByQuat(axis, mQuat);
 
       mAxisX =result.get0();
       mAxisY =result.get1();
@@ -181,61 +155,6 @@ public class RubikSurfaceView extends GLSurfaceView
       mAxisY /= len;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return quat1*quat2
-
-    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
-      {
-      float qx = quat1.get0();
-      float qy = quat1.get1();
-      float qz = quat1.get2();
-      float qw = quat1.get3();
-
-      float rx = quat2.get0();
-      float ry = quat2.get1();
-      float rz = quat2.get2();
-      float rw = quat2.get3();
-
-      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-      return new Static4D(tx,ty,tz,tw);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
-
-    public static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
-      {
-      float qx = quat.get0();
-      float qy = quat.get1();
-      float qz = quat.get2();
-      float qw = quat.get3();
-
-      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
-      Static4D tmp = quatMultiply(quat,vector);
-
-      return quatMultiply(tmp,quatInverted);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
-
-    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
-      {
-      float qx = quat.get0();
-      float qy = quat.get1();
-      float qz = quat.get2();
-      float qw = quat.get3();
-
-      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
-      Static4D tmp = quatMultiply(quatInverted,vector);
-
-      return quatMultiply(tmp,quat);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     private void addSpeedProbe(float x, float y)
@@ -312,8 +231,8 @@ public class RubikSurfaceView extends GLSurfaceView
         CAMERA_POINT.set2( object==null ? 1.21f : object.getCameraDist() );
 
         Static4D touchPoint = new Static4D(x, y, 0, 0);
-        Static4D rotatedTouchPoint= rotateVectorByInvertedQuat(touchPoint, mQuat);
-        Static4D rotatedCamera= rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
+        Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, mQuat);
+        Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
 
         if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera) )
           {
@@ -389,7 +308,7 @@ public class RubikSurfaceView extends GLSurfaceView
         float sinA =-(float)Math.sin(angleDiff);
         float cosA = (float)Math.cos(angleDiff);
 
-        Static4D dragQuat = quatMultiply(new Static4D(0,0,sinA,cosA), mQuat);
+        Static4D dragQuat = QuatHelper.quatMultiply(new Static4D(0,0,sinA,cosA), mQuat);
         mTemp.set(dragQuat);
 
         mRotAngle = angleNow;
@@ -402,7 +321,7 @@ public class RubikSurfaceView extends GLSurfaceView
         }
       else
         {
-        Static4D dragQuat = quatMultiply(quatFromDrag(mX-x,y-mY), mQuat);
+        Static4D dragQuat = QuatHelper.quatMultiply(QuatHelper.quatFromDrag(mX-x,y-mY), mQuat);
         mTemp.set(dragQuat);
         }
 
@@ -474,7 +393,7 @@ public class RubikSurfaceView extends GLSurfaceView
       int numLayers = object.getNumLayers();
 
       Static4D touchPoint2 = new Static4D(x, y, 0, 0);
-      Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuat);
+      Static4D rotatedTouchPoint2= QuatHelper.rotateVectorByInvertedQuat(touchPoint2, mQuat);
       Static2D res = mMovement.newRotation(numLayers,rotatedTouchPoint2);
 
       mCurrentAxis = (int)res.get0();
diff --git a/src/main/java/org/distorted/objects/Cubit.java b/src/main/java/org/distorted/objects/Cubit.java
index a8e044f6..ffcc1a14 100644
--- a/src/main/java/org/distorted/objects/Cubit.java
+++ b/src/main/java/org/distorted/objects/Cubit.java
@@ -21,8 +21,8 @@ package org.distorted.objects;
 
 import android.content.SharedPreferences;
 
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.type.Static4D;
-import org.distorted.main.RubikSurfaceView;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -141,7 +141,7 @@ class Cubit
     for(int i=0; i<len; i++)
       {
       cubitCenter =  new Static4D(mCurrentPosition[3*i], mCurrentPosition[3*i+1], mCurrentPosition[3*i+2], 0);
-      rotatedCenter = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat);
+      rotatedCenter = QuatHelper.rotateVectorByQuat( cubitCenter, quat);
 
       mCurrentPosition[3*i  ] = rotatedCenter.get0();
       mCurrentPosition[3*i+1] = rotatedCenter.get1();
@@ -189,7 +189,7 @@ class Cubit
 
   int removeRotationNow(Static4D quat)
     {
-    Static4D q = RubikSurfaceView.quatMultiply(quat,mParent.QUATS[mQuatIndex]);
+    Static4D q = QuatHelper.quatMultiply(quat,mParent.QUATS[mQuatIndex]);
     mQuatIndex = normalizeScrambleQuat(q);
 
     modifyCurrentPosition(quat);
diff --git a/src/main/java/org/distorted/objects/TwistyDino4.java b/src/main/java/org/distorted/objects/TwistyDino4.java
index 2a1a4f0a..b2318367 100644
--- a/src/main/java/org/distorted/objects/TwistyDino4.java
+++ b/src/main/java/org/distorted/objects/TwistyDino4.java
@@ -21,12 +21,12 @@ package org.distorted.objects;
 
 import android.content.res.Resources;
 
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshSquare;
 import org.distorted.library.type.Static4D;
 import org.distorted.main.R;
-import org.distorted.main.RubikSurfaceView;
 
 import java.util.Random;
 
@@ -57,7 +57,7 @@ public class TwistyDino4 extends TwistyDino
     float MAXDIFF = 0.01f;
     float[] center= CENTERS[centerNum];
     Static4D sc = new Static4D(center[0], center[1], center[2], 1.0f);
-    Static4D result = RubikSurfaceView.rotateVectorByQuat(sc,QUATS[quatNum]);
+    Static4D result = QuatHelper.rotateVectorByQuat(sc,QUATS[quatNum]);
     int numCenters = CENTERS.length;
 
     float x = result.get0();
diff --git a/src/main/java/org/distorted/objects/TwistyKilominx.java b/src/main/java/org/distorted/objects/TwistyKilominx.java
index ea2f9634..d5347a5e 100644
--- a/src/main/java/org/distorted/objects/TwistyKilominx.java
+++ b/src/main/java/org/distorted/objects/TwistyKilominx.java
@@ -25,6 +25,7 @@ import android.graphics.Paint;
 
 import org.distorted.helpers.FactoryCubit;
 import org.distorted.helpers.FactorySticker;
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedTexture;
@@ -33,7 +34,6 @@ import org.distorted.library.mesh.MeshSquare;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.main.R;
-import org.distorted.main.RubikSurfaceView;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -158,9 +158,9 @@ public class TwistyKilominx extends TwistyMinx
     {
     Static4D quat = QUATS[QUAT_CORNER_INDICES[corner]];
 
-    mCurrCornerV[0] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[0],quat);
-    mCurrCornerV[1] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[1],quat);
-    mCurrCornerV[2] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[2],quat);
+    mCurrCornerV[0] = QuatHelper.rotateVectorByQuat(mBasicCornerV[0],quat);
+    mCurrCornerV[1] = QuatHelper.rotateVectorByQuat(mBasicCornerV[1],quat);
+    mCurrCornerV[2] = QuatHelper.rotateVectorByQuat(mBasicCornerV[2],quat);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyMegaminx.java b/src/main/java/org/distorted/objects/TwistyMegaminx.java
index c3ffe471..bc449022 100644
--- a/src/main/java/org/distorted/objects/TwistyMegaminx.java
+++ b/src/main/java/org/distorted/objects/TwistyMegaminx.java
@@ -25,6 +25,7 @@ import android.graphics.Paint;
 
 import org.distorted.helpers.FactoryCubit;
 import org.distorted.helpers.FactorySticker;
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedTexture;
@@ -33,7 +34,6 @@ import org.distorted.library.mesh.MeshSquare;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.main.R;
-import org.distorted.main.RubikSurfaceView;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -147,9 +147,9 @@ public class TwistyMegaminx extends TwistyMinx
     {
     Static4D quat = QUATS[QUAT_CORNER_INDICES[corner]];
 
-    mCurrCornerV[0] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[0],quat);
-    mCurrCornerV[1] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[1],quat);
-    mCurrCornerV[2] = RubikSurfaceView.rotateVectorByQuat(mBasicCornerV[2],quat);
+    mCurrCornerV[0] = QuatHelper.rotateVectorByQuat(mBasicCornerV[0],quat);
+    mCurrCornerV[1] = QuatHelper.rotateVectorByQuat(mBasicCornerV[1],quat);
+    mCurrCornerV[2] = QuatHelper.rotateVectorByQuat(mBasicCornerV[2],quat);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/objects/TwistyObject.java b/src/main/java/org/distorted/objects/TwistyObject.java
index e0ffe216..0ec57f5c 100644
--- a/src/main/java/org/distorted/objects/TwistyObject.java
+++ b/src/main/java/org/distorted/objects/TwistyObject.java
@@ -28,6 +28,7 @@ import android.graphics.Paint;
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
 import org.distorted.helpers.FactoryCubit;
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.effect.Effect;
 import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectQuaternion;
@@ -48,7 +49,6 @@ import org.distorted.library.type.Static1D;
 import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.main.BuildConfig;
-import org.distorted.main.RubikSurfaceView;
 
 import java.io.DataInputStream;
 import java.io.IOException;
@@ -466,7 +466,7 @@ public abstract class TwistyObject extends DistortedNode
 
   int mulQuat(int q1, int q2)
     {
-    Static4D result = RubikSurfaceView.quatMultiply(QUATS[q1],QUATS[q2]);
+    Static4D result = QuatHelper.quatMultiply(QUATS[q1],QUATS[q2]);
 
     float rX = result.get0();
     float rY = result.get1();
@@ -533,8 +533,8 @@ public abstract class TwistyObject extends DistortedNode
                Static4D quat2 = QUATS[cubit.mQuatIndex];
 
                Static4D cubitCenter = new Static4D( orig[0], orig[1], orig[2], 0);              // not used for bandaged objects,
-               Static4D rotated1 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat1 );   // only check the first position
-               Static4D rotated2 = RubikSurfaceView.rotateVectorByQuat( cubitCenter, quat2 );
+               Static4D rotated1 = QuatHelper.rotateVectorByQuat( cubitCenter, quat1 );   // only check the first position
+               Static4D rotated2 = QuatHelper.rotateVectorByQuat( cubitCenter, quat2 );
 
                rotated1.get(mTmp1, 0, 0, 0);
                rotated2.get(mTmp2, 0, 0, 0);
@@ -974,8 +974,8 @@ public abstract class TwistyObject extends DistortedNode
   abstract float returnMultiplier();
   abstract float[][] getCuts(int numLayers);
   abstract boolean shouldResetTextureMaps();
-  abstract Static3D[] getRotationAxis();
 
+  public abstract Static3D[] getRotationAxis();
   public abstract boolean isSolved();
   public abstract int[] getBasicAngle();
   public abstract String retObjectString();
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index 47f22cfc..07eddc0a 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -397,7 +397,8 @@ public class RubikScreenPlay extends RubikScreenBase
               pDiag.show( act.getSupportFragmentManager(), RubikDialogPattern.getDialogTag() );
               break;
       case 2: RubikControl control = RubikControl.getInstance();
-              control.animateAll(act);
+              //control.animateAll(act);
+              control.animateRotate(act);
               break;
       case 3: ScreenList.switchScreen(act, ScreenList.SVER);
               break;
diff --git a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
index d9b1df8d..d4aec5a6 100644
--- a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
+++ b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
@@ -30,26 +30,22 @@ import android.view.MotionEvent;
 
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
+import org.distorted.helpers.QuatHelper;
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
 import org.distorted.objects.Movement;
 import org.distorted.objects.TwistyObject;
 
+import static org.distorted.main.RubikSurfaceView.INVALID_POINTER_ID;
+import static org.distorted.main.RubikSurfaceView.NUM_SPEED_PROBES;
+import static org.distorted.main.RubikSurfaceView.ROTATION_SENSITIVITY;
+import static org.distorted.main.RubikSurfaceView.SWIPING_SENSITIVITY;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class TutorialSurfaceView extends GLSurfaceView
 {
-    private static final int NUM_SPEED_PROBES = 10;
-    private static final int INVALID_POINTER_ID = -1;
-
-    // Moving the finger from the middle of the vertical screen to the right edge will rotate a
-    // given face by SWIPING_SENSITIVITY/2 degrees.
-    private final static int SWIPING_SENSITIVITY  = 240;
-    // Moving the finger by 0.3 of an inch will start a Rotation.
-    private final static float ROTATION_SENSITIVITY = 0.3f;
-
-    private final Static4D CAMERA_POINT = new Static4D(0, 0, 1, 0);
-
+    private final Static4D CAMERA_POINT = new Static4D(0, 0, 0, 0);
     private TutorialRenderer mRenderer;
     private TutorialPreRender mPreRender;
     private Movement mMovement;
@@ -125,40 +121,13 @@ public class TutorialSurfaceView extends GLSurfaceView
       mMovement = movement;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private Static4D quatFromDrag(float dragX, float dragY)
-      {
-      float axisX = dragY;  // inverted X and Y - rotation axis is perpendicular to (dragX,dragY)
-      float axisY = dragX;  // Why not (-dragY, dragX) ? because Y axis is also inverted!
-      float axisZ = 0;
-      float axisL = (float)Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
-
-      if( axisL>0 )
-        {
-        axisX /= axisL;
-        axisY /= axisL;
-        axisZ /= axisL;
-
-        float ratio = axisL;
-        ratio = ratio - (int)ratio;     // the cos() is only valid in (0,Pi)
-
-        float cosA = (float)Math.cos(Math.PI*ratio);
-        float sinA = (float)Math.sqrt(1-cosA*cosA);
-
-        return new Static4D(axisX*sinA, axisY*sinA, axisZ*sinA, cosA);
-        }
-
-      return new Static4D(0f, 0f, 0f, 1f);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // cast the 3D axis we are currently rotating along (which is already casted to the surface of the
 // currently touched face AND converted into a 4D vector - fourth 0) to a 2D in-screen-surface axis
 
     private void computeCurrentAxis(Static4D axis)
       {
-      Static4D result = rotateVectorByQuat(axis, mQuat);
+      Static4D result = QuatHelper.rotateVectorByQuat(axis, mQuat);
 
       mAxisX =result.get0();
       mAxisY =result.get1();
@@ -168,61 +137,6 @@ public class TutorialSurfaceView extends GLSurfaceView
       mAxisY /= len;
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return quat1*quat2
-
-    public static Static4D quatMultiply( Static4D quat1, Static4D quat2 )
-      {
-      float qx = quat1.get0();
-      float qy = quat1.get1();
-      float qz = quat1.get2();
-      float qw = quat1.get3();
-
-      float rx = quat2.get0();
-      float ry = quat2.get1();
-      float rz = quat2.get2();
-      float rw = quat2.get3();
-
-      float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-      float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-      float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-      float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-      return new Static4D(tx,ty,tz,tw);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// rotate 'vector' by quat  ( i.e. return quat*vector*(quat^-1) )
-
-    public static Static4D rotateVectorByQuat(Static4D vector, Static4D quat)
-      {
-      float qx = quat.get0();
-      float qy = quat.get1();
-      float qz = quat.get2();
-      float qw = quat.get3();
-
-      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
-      Static4D tmp = quatMultiply(quat,vector);
-
-      return quatMultiply(tmp,quatInverted);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// rotate 'vector' by quat^(-1)  ( i.e. return (quat^-1)*vector*quat )
-
-    public static Static4D rotateVectorByInvertedQuat(Static4D vector, Static4D quat)
-      {
-      float qx = quat.get0();
-      float qy = quat.get1();
-      float qz = quat.get2();
-      float qw = quat.get3();
-
-      Static4D quatInverted= new Static4D(-qx,-qy,-qz,qw);
-      Static4D tmp = quatMultiply(quatInverted,vector);
-
-      return quatMultiply(tmp,quat);
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     private void addSpeedProbe(float x, float y)
@@ -285,30 +199,33 @@ public class TutorialSurfaceView extends GLSurfaceView
 
     private void setUpDragOrRotate(float x, float y)
       {
-        Static4D touchPoint = new Static4D(x, y, 0, 0);
-        Static4D rotatedTouchPoint= rotateVectorByInvertedQuat(touchPoint, mQuat);
-        Static4D rotatedCamera= rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
+      TwistyObject object = mPreRender.getObject();
+      CAMERA_POINT.set2( object==null ? 1.21f : object.getCameraDist() );
 
-        if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera) )
-          {
-          mDragging           = false;
-          mContinuingRotation = false;
-          mBeginningRotation= !mPreRender.isTouchBlocked();
-          }
-        else
-          {
-          final TutorialActivity act = (TutorialActivity)getContext();
-          boolean locked      = act.isLocked();
-          mDragging           = !locked;
-          mContinuingRotation = false;
-          mBeginningRotation  = false;
+      Static4D touchPoint = new Static4D(x, y, 0, 0);
+      Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, mQuat);
+      Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, mQuat);
 
-          if( !mDragging )
-            {
-            TutorialState state = act.getState();
-            state.reddenLock(act);
-            }
+      if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint,rotatedCamera) )
+        {
+        mDragging           = false;
+        mContinuingRotation = false;
+        mBeginningRotation= !mPreRender.isTouchBlocked();
+        }
+      else
+        {
+        final TutorialActivity act = (TutorialActivity)getContext();
+        boolean locked      = act.isLocked();
+        mDragging           = !locked;
+        mContinuingRotation = false;
+        mBeginningRotation  = false;
+
+        if( !mDragging )
+          {
+          TutorialState state = act.getState();
+          state.reddenLock(act);
           }
+        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -345,7 +262,7 @@ public class TutorialSurfaceView extends GLSurfaceView
         float sinA =-(float)Math.sin(angleDiff);
         float cosA = (float)Math.cos(angleDiff);
 
-        Static4D dragQuat = quatMultiply(new Static4D(0,0,sinA,cosA), mQuat);
+        Static4D dragQuat = QuatHelper.quatMultiply(new Static4D(0,0,sinA,cosA), mQuat);
         mTemp.set(dragQuat);
 
         mRotAngle = angleNow;
@@ -359,7 +276,7 @@ public class TutorialSurfaceView extends GLSurfaceView
         }
       else
         {
-        Static4D dragQuat = quatMultiply(quatFromDrag(mX-x,y-mY), mQuat);
+        Static4D dragQuat = QuatHelper.quatMultiply(QuatHelper.quatFromDrag(mX-x,y-mY), mQuat);
         mTemp.set(dragQuat);
         }
 
@@ -421,7 +338,7 @@ public class TutorialSurfaceView extends GLSurfaceView
       int numLayers = object.getNumLayers();
 
       Static4D touchPoint2 = new Static4D(x, y, 0, 0);
-      Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuat);
+      Static4D rotatedTouchPoint2= QuatHelper.rotateVectorByInvertedQuat(touchPoint2, mQuat);
       Static2D res = mMovement.newRotation(numLayers,rotatedTouchPoint2);
 
       mCurrentAxis = (int)res.get0();
