commit 314bffaf4eb0ebdaa4479cad25883c03d51224e6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun May 16 23:19:36 2021 +0200

    RubikControl: fixes; progress.

diff --git a/src/main/java/org/distorted/control/RubikControl.java b/src/main/java/org/distorted/control/RubikControl.java
index 7d913d80..9f556e61 100644
--- a/src/main/java/org/distorted/control/RubikControl.java
+++ b/src/main/java/org/distorted/control/RubikControl.java
@@ -30,6 +30,8 @@ import java.lang.ref.WeakReference;
 
 public class RubikControl implements EffectListener
   {
+  private static RubikControl mControl;
+
   WeakReference<RubikActivity> mRefAct;
   boolean mWholeReturned, mRotateReturned;
 
@@ -127,14 +129,30 @@ public class RubikControl implements EffectListener
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC
 
-  public RubikControl()
+  private RubikControl()
     {
     mWhole = new RubikControlWhole(this);
     mRotate= new RubikControlRotate(this);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC
+
+  public static RubikControl getInstance()
+    {
+    if( mControl==null ) mControl = new RubikControl();
+
+    return mControl;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void postDrawFrame(long time)
+    {
+    mWhole.postDrawFrame(time);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void effectFinished(long effectID)
diff --git a/src/main/java/org/distorted/control/RubikControlWhole.java b/src/main/java/org/distorted/control/RubikControlWhole.java
index 1a5cf5e6..4d2699a3 100644
--- a/src/main/java/org/distorted/control/RubikControlWhole.java
+++ b/src/main/java/org/distorted/control/RubikControlWhole.java
@@ -54,11 +54,13 @@ class RubikControlWhole implements EffectListener
 
   private static final int[] DUR = { D1, D2, D3, D2, D1/4, 3*D1/4, D1/4, D2, D4, D5, D4, D2, D1 };
 
+  private float X0, X1, X2, Y1, D, s001, s014, s033, F;
+
   private final RubikControl mControl;
   private DistortedEffects[] mEffects;
   private DistortedNode[] mNodes;
   private long mEffectID;
-  private int mStageFinished, mWidth, mHeight;
+  private int mCurrentStage, mWidth, mHeight;
 
   private MeshQuad mQuad;
   private DistortedTexture mTextureShad, mTextureCirc;
@@ -73,6 +75,20 @@ class RubikControlWhole implements EffectListener
   private MatrixEffectMove mMoveHand2, mMoveShad2;
   private MatrixEffectScale mScaleHand2, mScaleShad2;
 
+  private Dynamic3D mDyn1, mDyn2;
+  private Static3D mPosition1, mPosition2;
+  private float[] mHandPosition;
+  private long mLastTime, mDiffTime;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void setPostFrame(boolean on)
+     {
+     RubikActivity act = mControl.getActivity();
+     act.setControlState(on);
+     mLastTime = -1;
+     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    private Bitmap openBitmap(RubikActivity act, int resource)
@@ -109,7 +125,7 @@ class RubikControlWhole implements EffectListener
     mDynScaleHand1.resetToBeginning();
     mDynScaleShad1.resetToBeginning();
 
-    mStageFinished = stage;
+    mCurrentStage = stage;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -133,6 +149,20 @@ class RubikControlWhole implements EffectListener
     mDynScaleShad2.resetToBeginning();
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void resetDynamics3(int stage)
+    {
+    int dur = DUR[stage-1];
+
+    mDyn1.removeAll();
+    mDyn2.removeAll();
+    mDyn1.setDuration(dur);
+    mDyn2.setDuration(dur);
+    mDyn1.resetToBeginning();
+    mDyn2.resetToBeginning();
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // first finger appears and approaches the screen
 
@@ -141,24 +171,16 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(1);
     resetDynamics2(1);
 
-    float x0 = mWidth*0.55f;
-    float x1 = mWidth*0.45f;
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float s  = mWidth*0.33f;
-
-    Static3D point0h = new Static3D(-x0    ,-y1    , z);
-    Static3D point1h = new Static3D(-x1    ,-y1    , z);
-    Static3D point2h = new Static3D(-x2    ,-y1    , z);
-    Static3D point3h = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point0s = new Static3D(-x0+2*d,-y1+2*d, z);
-    Static3D point1s = new Static3D(-x1+2*d,-y1+2*d, z);
-    Static3D point2s = new Static3D(-x2+2*d,-y1+2*d, z);
-    Static3D point3s = new Static3D(-x2  +d,-y1  +d, z);
-
-    Static3D pointSc = new Static3D(s,s,s);
+    Static3D point0h = new Static3D(-X0    ,-Y1    , 0);
+    Static3D point1h = new Static3D(-X1    ,-Y1    , 0);
+    Static3D point2h = new Static3D(-X2    ,-Y1    , 0);
+    Static3D point3h = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point0s = new Static3D(-X0+2*D,-Y1+2*D, 0);
+    Static3D point1s = new Static3D(-X1+2*D,-Y1+2*D, 0);
+    Static3D point2s = new Static3D(-X2+2*D,-Y1+2*D, 0);
+    Static3D point3s = new Static3D(-X2  +D,-Y1  +D, 0);
+
+    Static3D pointSc = new Static3D(s033,s033,s033);
 
     mDynMoveHand1.add(point0h);
     mDynMoveHand1.add(point1h);
@@ -183,18 +205,10 @@ class RubikControlWhole implements EffectListener
     {
     resetDynamics1(2);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float sS = mWidth*0.0001f;
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point3h = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D scaleS  = new Static3D(sS,sS,sS);
-    Static3D scaleF  = new Static3D(sF,sF,sF);
-    Static3D pointH  = new Static3D(sH,sH,sH);
+    Static3D point3h = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D scaleS  = new Static3D(s001,s001,s001);
+    Static3D scaleF  = new Static3D(s014,s014,s014);
+    Static3D pointH  = new Static3D(s033,s033,s033);
 
     mDynMoveHand1.add(point3h);
     mDynMoveShad1.add(point3h);
@@ -213,52 +227,38 @@ class RubikControlWhole implements EffectListener
   private void setEffectsStage3()
     {
     resetDynamics1(3);
+    resetDynamics3(3);
 
-    float x2 = mWidth*0.35f;
-
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float sH = mWidth*0.33f;
-    float sF = mWidth*0.14f;
-
-    Static3D scaleS = new Static3D(sF,sF,sF);
-    Static3D pointH = new Static3D(sH,sH,sH);
-    Static3D point1 = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point2 = new Static3D(     +d,-y1  +d, z);
-    Static3D point3 = new Static3D(+x2  +d,-y1  +d, z);
-    Static3D point4 = new Static3D(+x2  +d,     +d, z);
-    Static3D point5 = new Static3D(+x2  +d,+y1  +d, z);
-    Static3D point6 = new Static3D(     +d,     +d, z);
+    Static3D scaleS = new Static3D(s014,s014,s014);
+    Static3D pointH = new Static3D(s033,s033,s033);
+    Static3D point1 = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point2 = new Static3D(     +D,-Y1  +D, 0);
+    Static3D point3 = new Static3D(+X2  +D,-Y1  +D, 0);
+    Static3D point4 = new Static3D(+X2  +D,     +D, 0);
+    Static3D point5 = new Static3D(+X2  +D,+Y1  +D, 0);
+    Static3D point6 = new Static3D(     +D,     +D, 0);
 
     mDynScaleHand1.add(pointH);
     mDynScaleShad1.add(scaleS);
 
-    mDynMoveHand1.add(point1);
-    mDynMoveHand1.add(point1);
-    mDynMoveHand1.add(point2);
-    mDynMoveHand1.add(point3);
-    mDynMoveHand1.add(point3);
-    mDynMoveHand1.add(point4);
-    mDynMoveHand1.add(point5);
-    mDynMoveHand1.add(point5);
-    mDynMoveHand1.add(point6);
-    mDynMoveHand1.add(point1);
-    mDynMoveHand1.add(point1);
+    mDyn1.add(point1);
+    mDyn1.add(point1);
+    mDyn1.add(point2);
+    mDyn1.add(point3);
+    mDyn1.add(point3);
+    mDyn1.add(point4);
+    mDyn1.add(point5);
+    mDyn1.add(point5);
+    mDyn1.add(point6);
+    mDyn1.add(point1);
+    mDyn1.add(point1);
 
-    mDynMoveShad1.add(point1);
-    mDynMoveShad1.add(point1);
-    mDynMoveShad1.add(point2);
-    mDynMoveShad1.add(point3);
-    mDynMoveShad1.add(point3);
-    mDynMoveShad1.add(point4);
-    mDynMoveShad1.add(point5);
-    mDynMoveShad1.add(point5);
-    mDynMoveShad1.add(point6);
-    mDynMoveShad1.add(point1);
-    mDynMoveShad1.add(point1);
+    mPosition1.set(point1);
 
-    mMoveHand1.notifyWhenFinished(this);
+    mDynMoveHand1.add(mPosition1);
+    mDynMoveShad1.add(mPosition1);
+
+    setPostFrame(true);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -268,18 +268,10 @@ class RubikControlWhole implements EffectListener
     {
     resetDynamics1(4);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float sF = mWidth*0.0001f;
-    float sS = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point3h = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D scaleS  = new Static3D(sS,sS,sS);
-    Static3D scaleF  = new Static3D(sF,sF,sF);
-    Static3D pointH  = new Static3D(sH,sH,sH);
+    Static3D point3h = new Static3D(-X2+D,-Y1+D, 0);
+    Static3D scaleS  = new Static3D(s014,s014,s014);
+    Static3D scaleF  = new Static3D(s001,s001,s001);
+    Static3D pointH  = new Static3D(s033,s033,s033);
 
     mDynMoveHand1.add(point3h);
     mDynMoveShad1.add(point3h);
@@ -297,16 +289,10 @@ class RubikControlWhole implements EffectListener
     {
     resetDynamics1(5);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float s  = mWidth*0.33f;
-
-    Static3D pointH = new Static3D(-x2    ,-y1    , z);
-    Static3D point0 = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D pointS = new Static3D(-x2+2*d,-y1+2*d, z);
-    Static3D pointSc = new Static3D(s,s,s);
+    Static3D pointH = new Static3D(-X2    ,-Y1    , 0);
+    Static3D point0 = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D pointS = new Static3D(-X2+2*D,-Y1+2*D, 0);
+    Static3D pointSc = new Static3D(s033,s033,s033);
 
     mDynScaleHand1.add(pointSc);
     mDynScaleShad1.add(pointSc);
@@ -327,30 +313,22 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(6);
     resetDynamics2(6);
 
-    float x0 = mWidth*0.55f;
-    float x1 = mWidth*0.45f;
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float s  = mWidth*0.33f;
-
-    Static3D pointH = new Static3D(-x2    ,-y1    , z);
-    Static3D pointS = new Static3D(-x2+2*d,-y1+2*d, z);
-    Static3D pointSc= new Static3D(s,s,s);
+    Static3D pointH = new Static3D(-X2    ,-Y1    , 0);
+    Static3D pointS = new Static3D(-X2+2*D,-Y1+2*D, 0);
+    Static3D pointSc= new Static3D(s033,s033,s033);
 
     mDynScaleHand1.add(pointSc);
     mDynScaleShad1.add(pointSc);
     mDynMoveHand1.add(pointH);
     mDynMoveShad1.add(pointS);
 
-    Static3D point0h = new Static3D( x0    , y1    , z);
-    Static3D point1h = new Static3D( x1    , y1    , z);
-    Static3D point2h = new Static3D( x2    , y1    , z);
-    Static3D point0s = new Static3D( x0+2*d, y1+2*d, z);
-    Static3D point1s = new Static3D( x1+2*d, y1+2*d, z);
-    Static3D point2s = new Static3D( x2+2*d, y1+2*d, z);
-    Static3D pointSm= new Static3D(-s,s,s);
+    Static3D point0h = new Static3D( X0    , Y1    , 0);
+    Static3D point1h = new Static3D( X1    , Y1    , 0);
+    Static3D point2h = new Static3D( X2    , Y1    , 0);
+    Static3D point0s = new Static3D( X0+2*D, Y1+2*D, 0);
+    Static3D point1s = new Static3D( X1+2*D, Y1+2*D, 0);
+    Static3D point2s = new Static3D( X2+2*D, Y1+2*D, 0);
+    Static3D pointSm= new Static3D(-s033,s033,s033);
 
     mDynMoveHand2.add(point0h);
     mDynMoveHand2.add(point1h);
@@ -374,16 +352,10 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(7);
     resetDynamics2(7);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float s  = mWidth*0.33f;
-
-    Static3D point1H = new Static3D(-x2    ,-y1    , z);
-    Static3D point1F = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point1S = new Static3D(-x2+2*d,-y1+2*d, z);
-    Static3D point1Sc= new Static3D(s,s,s);
+    Static3D point1H = new Static3D(-X2    ,-Y1    , 0);
+    Static3D point1F = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point1S = new Static3D(-X2+2*D,-Y1+2*D, 0);
+    Static3D point1Sc= new Static3D(s033,s033,s033);
 
     mDynScaleHand1.add(point1Sc);
     mDynScaleShad1.add(point1Sc);
@@ -392,10 +364,10 @@ class RubikControlWhole implements EffectListener
     mDynMoveShad1.add(point1S);
     mDynMoveShad1.add(point1F);
 
-    Static3D point2H = new Static3D( x2    , y1    , z);
-    Static3D point2F = new Static3D( x2  +d, y1  +d, z);
-    Static3D point2S = new Static3D( x2+2*d, y1+2*d, z);
-    Static3D point2Sc= new Static3D(-s,s,s);
+    Static3D point2H = new Static3D( X2    , Y1    , 0);
+    Static3D point2F = new Static3D( X2  +D, Y1  +D, 0);
+    Static3D point2S = new Static3D( X2+2*D, Y1+2*D, 0);
+    Static3D point2Sc= new Static3D(-s033,s033,s033);
 
     mDynScaleHand2.add(point2Sc);
     mDynScaleShad2.add(point2Sc);
@@ -415,22 +387,14 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(8);
     resetDynamics2(8);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float sS = mWidth*0.0001f;
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point1h= new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point2h= new Static3D( x2  +d, y1  +d, z);
-    Static3D scale1S = new Static3D( sS,sS,sS);
-    Static3D scale1F = new Static3D( sF,sF,sF);
-    Static3D point1H = new Static3D( sH,sH,sH);
-    Static3D scale2S = new Static3D(-sS,sS,sS);
-    Static3D scale2F = new Static3D(-sF,sF,sF);
-    Static3D point2H = new Static3D(-sH,sH,sH);
+    Static3D point1h= new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point2h= new Static3D( X2  +D, Y1  +D, 0);
+    Static3D scale1S = new Static3D( s001,s001,s001);
+    Static3D scale1F = new Static3D( s014,s014,s014);
+    Static3D point1H = new Static3D( s033,s033,s033);
+    Static3D scale2S = new Static3D(-s001,s001,s001);
+    Static3D scale2F = new Static3D(-s014,s014,s014);
+    Static3D point2H = new Static3D(-s033,s033,s033);
 
     mDynMoveHand1.add(point1h);
     mDynMoveShad1.add(point1h);
@@ -444,7 +408,7 @@ class RubikControlWhole implements EffectListener
     mDynScaleShad2.add(scale2S);
     mDynScaleShad2.add(scale2F);
 
-    mScaleShad1.notifyWhenFinished(this);
+    mScaleShad2.notifyWhenFinished(this);
 
     mNodes[0].changeInputSurface(mTextureCirc);
     mNodes[1].changeInputSurface(mTextureCirc);
@@ -457,39 +421,36 @@ class RubikControlWhole implements EffectListener
     {
     resetDynamics1(9);
     resetDynamics2(9);
+    resetDynamics3(9);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float f  = 0.60f;
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point1s= new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point2s= new Static3D( x2  +d, y1  +d, z);
-    Static3D point1f= new Static3D((-x2  +d)*f,(-y1  +d)*f, z);
-    Static3D point2f= new Static3D(( x2  +d)*f,( y1  +d)*f, z);
-    Static3D scale1F = new Static3D( sF,sF,sF);
-    Static3D point1H = new Static3D( sH,sH,sH);
-    Static3D scale2F = new Static3D(-sF,sF,sF);
-    Static3D point2H = new Static3D(-sH,sH,sH);
+    Static3D point1s= new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point2s= new Static3D( X2  +D, Y1  +D, 0);
+    Static3D point1f= new Static3D((-X2  +D)*F,(-Y1  +D)*F, 0);
+    Static3D point2f= new Static3D(( X2  +D)*F,( Y1  +D)*F, 0);
+    Static3D scale1F = new Static3D( s014,s014,s014);
+    Static3D point1H = new Static3D( s033,s033,s033);
+    Static3D scale2F = new Static3D(-s014,s014,s014);
+    Static3D point2H = new Static3D(-s033,s033,s033);
 
-    mDynMoveHand1.add(point1s);
-    mDynMoveHand1.add(point1f);
-    mDynMoveShad1.add(point1s);
-    mDynMoveShad1.add(point1f);
     mDynScaleHand1.add(point1H);
     mDynScaleShad1.add(scale1F);
-
-    mDynMoveHand2.add(point2s);
-    mDynMoveHand2.add(point2f);
-    mDynMoveShad2.add(point2s);
-    mDynMoveShad2.add(point2f);
     mDynScaleHand2.add(point2H);
     mDynScaleShad2.add(scale2F);
 
-    mMoveShad1.notifyWhenFinished(this);
+    mDyn1.add(point1s);
+    mDyn1.add(point1f);
+    mDyn2.add(point2s);
+    mDyn2.add(point2f);
+
+    mPosition1.set(point1s);
+    mPosition2.set(point2s);
+
+    mDynMoveHand1.add(mPosition1);
+    mDynMoveShad1.add(mPosition1);
+    mDynMoveHand2.add(mPosition2);
+    mDynMoveShad2.add(mPosition2);
+
+    setPostFrame(true);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -500,23 +461,15 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(10);
     resetDynamics2(10);
 
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float f  = 0.60f;
-
-    Static3D scale1F = new Static3D( sF,sF,sF);
-    Static3D point1H = new Static3D( sH,sH,sH);
-    Static3D scale2F = new Static3D(-sF,sF,sF);
-    Static3D point2H = new Static3D(-sH,sH,sH);
+    Static3D scale1F = new Static3D( s014,s014,s014);
+    Static3D point1H = new Static3D( s033,s033,s033);
+    Static3D scale2F = new Static3D(-s014,s014,s014);
+    Static3D point2H = new Static3D(-s033,s033,s033);
 
-    Static3D point0= new Static3D((-x2  +d)*f,(-y1  +d)*f, z);
-    Static3D point1= new Static3D((-x2  +d)*f,( y1  +d)*f, z);
-    Static3D point2= new Static3D(( x2  +d)*f,( y1  +d)*f, z);
-    Static3D point3= new Static3D(( x2  +d)*f,(-y1  +d)*f, z);
+    Static3D point0= new Static3D((-X2+D)*F,(-Y1+D)*F, 0);
+    Static3D point1= new Static3D((-X2+D)*F,( Y1+D)*F, 0);
+    Static3D point2= new Static3D(( X2+D)*F,( Y1+D)*F, 0);
+    Static3D point3= new Static3D(( X2+D)*F,(-Y1+D)*F, 0);
 
     mDynMoveHand1.add(point0);
     mDynMoveHand1.add(point1);
@@ -560,22 +513,14 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(11);
     resetDynamics2(11);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float f  = 0.60f;
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point1s= new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point2s= new Static3D( x2  +d, y1  +d, z);
-    Static3D point1f= new Static3D((-x2  +d)*f,(-y1  +d)*f, z);
-    Static3D point2f= new Static3D(( x2  +d)*f,( y1  +d)*f, z);
-    Static3D scale1F= new Static3D( sF,sF,sF);
-    Static3D point1H= new Static3D( sH,sH,sH);
-    Static3D scale2F= new Static3D(-sF,sF,sF);
-    Static3D point2H= new Static3D(-sH,sH,sH);
+    Static3D point1s= new Static3D( -X2+D   , -Y1+D   , 0);
+    Static3D point2s= new Static3D(  X2+D   ,  Y1+D   , 0);
+    Static3D point1f= new Static3D((-X2+D)*F,(-Y1+D)*F, 0);
+    Static3D point2f= new Static3D(( X2+D)*F,( Y1+D)*F, 0);
+    Static3D scale1F= new Static3D( s014,s014,s014);
+    Static3D point1H= new Static3D( s033,s033,s033);
+    Static3D scale2F= new Static3D(-s014,s014,s014);
+    Static3D point2H= new Static3D(-s033,s033,s033);
 
     mDynMoveHand1.add(point1f);
     mDynMoveHand1.add(point1s);
@@ -607,22 +552,14 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(12);
     resetDynamics2(12);
 
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float sS = mWidth*0.0001f;
-    float sF = mWidth*0.14f;
-    float sH = mWidth*0.33f;
-
-    Static3D point1h= new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point2h= new Static3D( x2  +d, y1  +d, z);
-    Static3D scale1S = new Static3D( sS,sS,sS);
-    Static3D scale1F = new Static3D( sF,sF,sF);
-    Static3D point1H = new Static3D( sH,sH,sH);
-    Static3D scale2S = new Static3D(-sS,sS,sS);
-    Static3D scale2F = new Static3D(-sF,sF,sF);
-    Static3D point2H = new Static3D(-sH,sH,sH);
+    Static3D point1h = new Static3D(-X2+D,-Y1+D, 0);
+    Static3D point2h = new Static3D( X2+D, Y1+D, 0);
+    Static3D scale1S = new Static3D( s014,s014,s014);
+    Static3D scale1F = new Static3D( s001,s001,s001);
+    Static3D point1H = new Static3D( s033,s033,s033);
+    Static3D scale2S = new Static3D(-s014,s014,s014);
+    Static3D scale2F = new Static3D(-s001,s001,s001);
+    Static3D point2H = new Static3D(-s033,s033,s033);
 
     mDynMoveHand1.add(point1h);
     mDynMoveShad1.add(point1h);
@@ -647,22 +584,14 @@ class RubikControlWhole implements EffectListener
     resetDynamics1(13);
     resetDynamics2(13);
 
-    float x0 = mWidth*0.55f;
-    float x1 = mWidth*0.45f;
-    float x2 = mWidth*0.35f;
-    float y1 = mHeight*0.28f;
-    float z  = 0;
-    float d  = mWidth*0.01f;
-    float s  = mWidth*0.33f;
-
-    Static3D point1_0 = new Static3D(-x2  +d,-y1  +d, z);
-    Static3D point11H = new Static3D(-x2    ,-y1    , z);
-    Static3D point12H = new Static3D(-x1    ,-y1    , z);
-    Static3D point13H = new Static3D(-x0    ,-y1    , z);
-    Static3D point11S = new Static3D(-x2+2*d,-y1+2*d, z);
-    Static3D point12S = new Static3D(-x1+2*d,-y1+2*d, z);
-    Static3D point13S = new Static3D(-x0+2*d,-y1+2*d, z);
-    Static3D point1Sc = new Static3D( s,s,s);
+    Static3D point1_0 = new Static3D(-X2  +D,-Y1  +D, 0);
+    Static3D point11H = new Static3D(-X2    ,-Y1    , 0);
+    Static3D point12H = new Static3D(-X1    ,-Y1    , 0);
+    Static3D point13H = new Static3D(-X0    ,-Y1    , 0);
+    Static3D point11S = new Static3D(-X2+2*D,-Y1+2*D, 0);
+    Static3D point12S = new Static3D(-X1+2*D,-Y1+2*D, 0);
+    Static3D point13S = new Static3D(-X0+2*D,-Y1+2*D, 0);
+    Static3D point1Sc = new Static3D( s033,s033,s033);
 
     mDynScaleHand1.add(point1Sc);
     mDynScaleShad1.add(point1Sc);
@@ -677,14 +606,14 @@ class RubikControlWhole implements EffectListener
     mDynMoveShad1.add(point12S);
     mDynMoveShad1.add(point13S);
 
-    Static3D point2_0 = new Static3D( x2  +d, y1  +d, z);
-    Static3D point21H = new Static3D( x2    , y1    , z);
-    Static3D point22H = new Static3D( x1    , y1    , z);
-    Static3D point23H = new Static3D( x0    , y1    , z);
-    Static3D point21S = new Static3D( x2+2*d, y1+2*d, z);
-    Static3D point22S = new Static3D( x1+2*d, y1+2*d, z);
-    Static3D point23S = new Static3D( x0+2*d, y1+2*d, z);
-    Static3D point2Sc= new Static3D(-s,s,s);
+    Static3D point2_0 = new Static3D( X2  +D, Y1  +D, 0);
+    Static3D point21H = new Static3D( X2    , Y1    , 0);
+    Static3D point22H = new Static3D( X1    , Y1    , 0);
+    Static3D point23H = new Static3D( X0    , Y1    , 0);
+    Static3D point21S = new Static3D( X2+2*D, Y1+2*D, 0);
+    Static3D point22S = new Static3D( X1+2*D, Y1+2*D, 0);
+    Static3D point23S = new Static3D( X0+2*D, Y1+2*D, 0);
+    Static3D point2Sc= new Static3D(-s033,s033,s033);
 
     mDynScaleHand2.add(point2Sc);
     mDynScaleShad2.add(point2Sc);
@@ -714,6 +643,18 @@ class RubikControlWhole implements EffectListener
 
     int time = DUR[0];
 
+    mDyn1 = new Dynamic3D(time,0.5f);
+    mDyn1.setMode(Dynamic.MODE_PATH);
+    mDyn1.setConvexity(0.0f);
+    mDyn2 = new Dynamic3D(time,0.5f);
+    mDyn2.setMode(Dynamic.MODE_PATH);
+    mDyn2.setConvexity(0.0f);
+
+    mPosition1 = new Static3D(0,0,0);
+    mPosition2 = new Static3D(0,0,0);
+
+    mHandPosition = new float[6];
+
     mDynMoveHand1 = new Dynamic3D(time,0.5f);
     mDynMoveHand1.setMode(Dynamic.MODE_PATH);
     mDynMoveHand1.setConvexity(0.0f);
@@ -763,6 +704,16 @@ class RubikControlWhole implements EffectListener
     DistortedScreen screen = mControl.getScreen();
     mWidth = screen.getWidth();
     mHeight= screen.getHeight();
+
+    X0 = mWidth*0.55f;
+    X1 = mWidth*0.45f;
+    X2 = mWidth*0.35f;
+    Y1 = mHeight*0.28f;
+    D  = mWidth*0.01f;
+    s001 = mWidth*0.0001f;
+    s014 = mWidth*0.14f;
+    s033 = mWidth*0.33f;
+    F  = 0.60f;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -838,7 +789,7 @@ class RubikControlWhole implements EffectListener
 
   public void effectFinished(long effectID)
     {
-    switch( mStageFinished )
+    switch( mCurrentStage )
       {
       case  1: setEffectsStage2(); break;
       case  2: setEffectsStage3(); break;
@@ -857,4 +808,39 @@ class RubikControlWhole implements EffectListener
                break;
       }
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void postDrawFrame(long time)
+    {
+    if( mLastTime<0 ) mLastTime = time;
+    else mDiffTime = time - mLastTime;
+    mLastTime = time;
+
+    switch( mCurrentStage )
+      {
+      case  3:
+
+      mDyn1.setDebug(true);
+      boolean finished = mDyn1.get( mHandPosition,0, time, mDiffTime);
+               mPosition1.set(mHandPosition[0], mHandPosition[1], mHandPosition[2]);
+               if( finished )
+                 {
+                 setPostFrame(false);
+                 effectFinished(0);
+                 }
+               break;
+      case  9: boolean finished1 = mDyn1.get( mHandPosition,0, time, mDiffTime);
+               boolean finished2 = mDyn2.get( mHandPosition,3, time, mDiffTime);
+               mPosition1.set(mHandPosition[0], mHandPosition[1], mHandPosition[2]);
+               mPosition2.set(mHandPosition[3], mHandPosition[4], mHandPosition[5]);
+               if( finished1 && finished2 )
+                 {
+                 setPostFrame(false);
+                 effectFinished(0);
+                 }
+               break;
+      default: android.util.Log.e("D", "WHAT? "+mCurrentStage);
+      }
+    }
   }
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 52728561..e8c0e34d 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -505,6 +505,15 @@ public class RubikActivity extends AppCompatActivity
         }
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setControlState(boolean on)
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      RubikRenderer renderer = view.getRenderer();
+      renderer.setControlState(on);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void acceptPrivacy()
diff --git a/src/main/java/org/distorted/main/RubikRenderer.java b/src/main/java/org/distorted/main/RubikRenderer.java
index 0fc0a086..0bad85d4 100644
--- a/src/main/java/org/distorted/main/RubikRenderer.java
+++ b/src/main/java/org/distorted/main/RubikRenderer.java
@@ -22,6 +22,7 @@ package org.distorted.main;
 import android.opengl.GLES30;
 import android.opengl.GLSurfaceView;
 
+import org.distorted.control.RubikControl;
 import org.distorted.effects.BaseEffect;
 import org.distorted.library.effect.EffectType;
 import org.distorted.library.effect.VertexEffectQuaternion;
@@ -47,6 +48,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
    private final Fps mFPS;
    private boolean mErrorShown;
    private boolean mDebugSent;
+   private RubikControl mControl;
 
    private static class Fps
      {
@@ -97,6 +99,13 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
      mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
      }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void setControlState(boolean on)
+     {
+     mControl = on ? RubikControl.getInstance() : null;
+     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // various things are done here delayed, 'after the next render' as not to be done mid-render and
 // cause artifacts.
@@ -108,6 +117,8 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
      mFPS.onRender(time);
      mView.getPreRender().preRender();
      mScreen.render(time);
+
+     if( mControl!=null ) mControl.postDrawFrame(time);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index fecf5623..47d2fdee 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -72,7 +72,6 @@ public class RubikScreenPlay extends RubikScreenBase
   private float mButtonSize, mMenuItemSize, mMenuTextSize;
   private int mColCount, mRowCount;
   private LinearLayout mPlayLayout;
-  private RubikControl mControlWhole;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -395,8 +394,8 @@ public class RubikScreenPlay extends RubikScreenBase
               pDiag.setArguments(pBundle);
               pDiag.show( act.getSupportFragmentManager(), RubikDialogPattern.getDialogTag() );
               break;
-      case 2: if( mControlWhole==null ) mControlWhole = new RubikControl();
-              mControlWhole.animateAll(act);
+      case 2: RubikControl control = RubikControl.getInstance();
+              control.animateAll(act);
               break;
       case 3: ScreenList.switchScreen(act, ScreenList.SVER);
               break;
