commit 8becce57d86e7089d54503b640fd96c3f76ac8e6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Mar 30 22:19:47 2020 +0100

    Major progress with Prretty Patterns - hopefully only initializing the Object remains!

diff --git a/src/main/java/org/distorted/effect/BaseEffect.java b/src/main/java/org/distorted/effect/BaseEffect.java
index b71531a2..27b60c84 100644
--- a/src/main/java/org/distorted/effect/BaseEffect.java
+++ b/src/main/java/org/distorted/effect/BaseEffect.java
@@ -27,8 +27,9 @@ import org.distorted.effect.scramble.ScrambleEffect;
 import org.distorted.effect.sizechange.SizeChangeEffect;
 import org.distorted.effect.solve.SolveEffect;
 import org.distorted.effect.win.WinEffect;
+import org.distorted.library.main.DistortedScreen;
 import org.distorted.magic.R;
-import org.distorted.magic.RubikRenderer;
+import org.distorted.magic.RubikPostRender;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -196,17 +197,17 @@ public class BaseEffect
 
   ////////////////////////////////////////////////////////////////////////////////
 
-    public long startEffect(RubikRenderer renderer) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+    public long startEffect(DistortedScreen screen, RubikPostRender post) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
       {
       Method method1 = mClass.getDeclaredMethod("create", int.class);
 
       Object value1 = method1.invoke(null,mCurrentType);
       BaseEffect baseEffect = (BaseEffect)value1;
 
-      Method method2 = mClass.getDeclaredMethod("start", int.class, RubikRenderer.class);
+      Method method2 = mClass.getDeclaredMethod("start", int.class, DistortedScreen.class, RubikPostRender.class);
 
       Integer translated = translatePos(mCurrentPos)+1;
-      Object value2 = method2.invoke(baseEffect,translated,renderer);
+      Object value2 = method2.invoke(baseEffect,translated,screen,post);
       return (Long)value2;
       }
 
diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
index 26c6a562..d34ef180 100644
--- a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
@@ -22,8 +22,9 @@ package org.distorted.effect.scramble;
 import org.distorted.effect.BaseEffect;
 import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikRenderer;
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
@@ -31,7 +32,7 @@ import java.util.Random;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public abstract class ScrambleEffect extends BaseEffect implements EffectListener
+public abstract class ScrambleEffect extends BaseEffect implements EffectListener, RubikPostRender.ActionFinishedListener
 {
   public enum Type
     {
@@ -62,9 +63,8 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
       }
     }
 
-  private EffectListener mListener;
+  private RubikPostRender mPostRender;
   private int mEffectReturned;
-  private long mCurrentBaseEffectID;
   private int mNumDoubleScramblesLeft, mNumScramblesLeft;
   private int mLastVector;
   private long mDurationSingleTurn;
@@ -154,7 +154,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
         android.util.Log.e("effect", "ERROR: "+mNumDoubleScramblesLeft);
         }
 
-      mCurrentBaseEffectID = mObject.addNewRotation(mLastVector, rowBitmap, angle*(360/mBasicAngle), durationMillis, this );
+      mPostRender.addRotation(this, mLastVector, rowBitmap, angle*(360/mBasicAngle), durationMillis);
       }
     else
       {
@@ -162,7 +162,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
       if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
         {
-        mListener.effectFinished(FAKE_EFFECT_ID);
+        mPostRender.effectFinished(FAKE_EFFECT_ID);
         }
       }
     }
@@ -242,15 +242,16 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void effectFinished(final long effectID)
+  public void onActionFinished(final long effectID)
     {
-    if( effectID == mCurrentBaseEffectID )
-      {
-      mObject.removeRotationNow();
-      addNewScramble();
-      return;
-      }
+    mObject.removeRotationNow();
+    addNewScramble();
+    }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
     for(int i=0; i<mCubeEffectNumber; i++)
       {
       long id = mCubeEffects[i].getID();
@@ -266,7 +267,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
           if( mNumScramblesLeft==0 )
             {
-            mListener.effectFinished(FAKE_EFFECT_ID);
+            mPostRender.effectFinished(FAKE_EFFECT_ID);
             }
           }
 
@@ -289,7 +290,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
           if( mNumScramblesLeft==0 )
             {
-            mListener.effectFinished(FAKE_EFFECT_ID);
+            mPostRender.effectFinished(FAKE_EFFECT_ID);
             }
           }
 
@@ -301,17 +302,17 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @SuppressWarnings("unused")
-  public long start(int duration, RubikRenderer renderer)
+  public long start(int duration, DistortedScreen screen, RubikPostRender post)
     {
-    mObject   = renderer.getObject();
-    mListener = renderer;
+    mObject     = post.getObject();
+    mPostRender = post;
 
     mObject.solve();
 
     mNumAxis    = mObject.getRotationAxis().length;
     mBasicAngle = mObject.getBasicAngle();
 
-    int numScrambles = renderer.getNumScrambles();
+    int numScrambles = post.getNumScrambles();
     int dura = (int)(duration*Math.pow(numScrambles,0.6f));
     createBaseEffects(dura,numScrambles);
     createEffects    (dura,numScrambles);
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
index 884e2a64..d8ff2082 100644
--- a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
@@ -24,7 +24,7 @@ import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikRenderer;
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
@@ -224,12 +224,12 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @SuppressWarnings("unused")
-  public long start(int duration, RubikRenderer renderer)
+  public long start(int duration, DistortedScreen screen, RubikPostRender post)
     {
-    mScreen   = renderer.getScreen();
-    mObject[0]= renderer.getOldObject();
-    mObject[1]= renderer.getObject();
-    mListener = renderer;
+    mScreen   = screen;
+    mObject[0]= post.getOldObject();
+    mObject[1]= post.getObject();
+    mListener = post;
     mDuration = duration;
 
     if( mObject[0]!=null )
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffect.java b/src/main/java/org/distorted/effect/solve/SolveEffect.java
index 4469a481..0b5f2d17 100644
--- a/src/main/java/org/distorted/effect/solve/SolveEffect.java
+++ b/src/main/java/org/distorted/effect/solve/SolveEffect.java
@@ -24,7 +24,7 @@ import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikRenderer;
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
@@ -197,11 +197,11 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @SuppressWarnings("unused")
-  public long start(int duration, RubikRenderer renderer)
+  public long start(int duration, DistortedScreen screen, RubikPostRender post)
     {
-    mScreen   = renderer.getScreen();
-    mObject   = renderer.getObject();
-    mListener = renderer;
+    mScreen   = screen;
+    mObject   = post.getObject();
+    mListener = post;
     mDuration = duration;
 
     createEffectsPhase0(mDuration);
diff --git a/src/main/java/org/distorted/effect/win/WinEffect.java b/src/main/java/org/distorted/effect/win/WinEffect.java
index 49cf3180..07cb8447 100644
--- a/src/main/java/org/distorted/effect/win/WinEffect.java
+++ b/src/main/java/org/distorted/effect/win/WinEffect.java
@@ -24,7 +24,7 @@ import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikRenderer;
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
@@ -163,11 +163,11 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @SuppressWarnings("unused")
-  public long start(int duration, RubikRenderer renderer)
+  public long start(int duration, DistortedScreen screen, RubikPostRender post)
     {
-    mScreen   = renderer.getScreen();
-    mObject   = renderer.getObject();
-    mListener = renderer;
+    mScreen   = screen;
+    mObject   = post.getObject();
+    mListener = post;
     mDuration = duration;
 
     createEffects(mDuration);
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index 6b28be51..88dde7cf 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -102,7 +102,7 @@ public class RubikActivity extends AppCompatActivity
         if( sizeIndex>=0 && sizeIndex<sizes.length )
           {
           success = true;
-          view.getRenderer().changeObject( obj, size, null );
+          view.getPostRender().changeObject( obj, size, null );
           }
 
         }
@@ -113,7 +113,7 @@ public class RubikActivity extends AppCompatActivity
         int s = RubikStatePlay.DEF_SIZE;
 
         play.setObjectAndSize(obj,s);
-        view.getRenderer().changeObject(obj,s, null);
+        view.getPostRender().changeObject(obj,s, null);
         }
       }
     
@@ -145,7 +145,7 @@ public class RubikActivity extends AppCompatActivity
 
       RubikState.savePreferences(editor);
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.getRenderer().savePreferences(editor);
+      view.getPostRender().savePreferences(editor);
 
       editor.apply();
       }
@@ -169,7 +169,7 @@ public class RubikActivity extends AppCompatActivity
       RubikState.restorePreferences(preferences);
 
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.getRenderer().restorePreferences(preferences);
+      view.getPostRender().restorePreferences(preferences);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -179,8 +179,16 @@ public class RubikActivity extends AppCompatActivity
     public RubikObject getObject()
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      RubikRenderer renderer = view.getRenderer();
-      return renderer.getObject();
+      RubikPostRender post = view.getPostRender();
+      return post.getObject();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public RubikPostRender getPostRender()
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      return view.getPostRender();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -188,11 +196,11 @@ public class RubikActivity extends AppCompatActivity
     public void changeObject(RubikObjectList object, int size, String moves)
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      RubikRenderer renderer = view.getRenderer();
+      RubikPostRender post = view.getPostRender();
 
-      if( renderer.canDrag() )
+      if( post.canDrag() )
         {
-        renderer.changeObject(object,size,moves);
+        post.changeObject(object,size,moves);
         }
       }
 
@@ -266,7 +274,7 @@ public class RubikActivity extends AppCompatActivity
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       RubikStatePlay play = (RubikStatePlay)RubikState.PLAY.getStateClass();
       int scramble = play.getPicker();
-      view.getRenderer().scrambleObject(scramble);
+      view.getPostRender().scrambleObject(scramble);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -274,6 +282,6 @@ public class RubikActivity extends AppCompatActivity
     public void Solve(View v)
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.getRenderer().solveObject();
+      view.getPostRender().solveObject();
       }
 }
diff --git a/src/main/java/org/distorted/magic/RubikPostRender.java b/src/main/java/org/distorted/magic/RubikPostRender.java
new file mode 100644
index 00000000..bb108826
--- /dev/null
+++ b/src/main/java/org/distorted/magic/RubikPostRender.java
@@ -0,0 +1,491 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.magic;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+import org.distorted.dialog.RubikDialogNewRecord;
+import org.distorted.dialog.RubikDialogSolved;
+import org.distorted.effect.BaseEffect;
+import org.distorted.library.message.EffectListener;
+import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectList;
+import org.distorted.scores.RubikScores;
+import org.distorted.uistate.RubikState;
+import org.distorted.uistate.RubikStateSolving;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikPostRender implements EffectListener
+  {
+  public interface ActionFinishedListener
+    {
+    void onActionFinished(long effectID);
+    }
+
+  private RubikSurfaceView mView;
+  private boolean mFinishRotation, mRemoveRotation, mAddRotation, mSetQuatCurrent, mSetQuatAccumulated;
+  private boolean mChangeObject, mSolveObject, mScrambleObject;
+  private boolean mCanRotate, mCanDrag, mCanUI;
+  private boolean mIsSolved;
+  private RubikObjectList mNextObject;
+  private int mNextSize;
+  private long mRotationFinishedID;
+  private long[] mEffectID;
+  private boolean mIsNewRecord;
+  private long mNewRecord;
+  private int mScreenWidth, mScreenHeight;
+  private SharedPreferences mPreferences;
+  private String mNextMoves;
+  private RubikObject mOldObject, mNewObject;
+  private int mScrambleObjectNum;
+  private int mAddRotationAxis, mAddRotationRowBitmap, mAddRotationAngle;
+  private long mAddRotationDuration;
+  private ActionFinishedListener mAddActionListener;
+  private long mAddRotationID;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikPostRender(RubikSurfaceView view)
+    {
+    mView = view;
+
+    mFinishRotation     = false;
+    mRemoveRotation     = false;
+    mAddRotation        = false;
+    mSetQuatCurrent     = false;
+    mSetQuatAccumulated = false;
+    mChangeObject       = false;
+    mSolveObject        = false;
+    mScrambleObject     = false;
+
+    mCanRotate   = true;
+    mCanDrag     = true;
+    mCanUI       = true;
+
+    mOldObject = null;
+    mNewObject = null;
+
+    mScreenWidth = mScreenHeight = 0;
+    mScrambleObjectNum = 0;
+
+    mEffectID = new long[BaseEffect.Type.LENGTH];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createObjectNow(RubikObjectList object, int size, String moves)
+    {
+    boolean firstTime = (mNewObject==null);
+
+    if( mOldObject!=null ) mOldObject.releaseResources();
+    mOldObject = mNewObject;
+
+    mNewObject = object.create(size, mView.getQuatCurrent(), mView.getQuatAccumulated(), moves);
+    mNewObject.createTexture();
+    mView.setMovement(object.getObjectMovementClass());
+
+    if( firstTime ) mNewObject.restorePreferences(mPreferences);
+
+    if( mScreenWidth!=0 )
+      {
+      mNewObject.recomputeScaleFactor(mScreenWidth, mScreenHeight);
+      }
+
+    mIsSolved = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do all 'adjustable' effects (SizeChange, Solve, Scramble)
+
+  private void doEffectNow(BaseEffect.Type type)
+    {
+    int index = type.ordinal();
+
+    try
+      {
+      mEffectID[index] = type.startEffect(mView.getRenderer().getScreen(),this);
+      }
+    catch( Exception ex )
+      {
+      android.util.Log.e("renderer", "exception starting effect: "+ex.getMessage());
+
+      mCanUI     = true;
+      mCanRotate = true;
+      mCanDrag   = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void removeRotationNow()
+    {
+    mRemoveRotation=false;
+    mNewObject.removeRotationNow();
+
+    boolean solved = mNewObject.isSolved();
+
+    if( solved && !mIsSolved )
+      {
+      if( RubikState.getCurrentState()==RubikState.SOLV )
+        {
+        RubikStateSolving solving = (RubikStateSolving)RubikState.SOLV.getStateClass();
+        mNewRecord = solving.stopCounting((RubikActivity)mView.getContext());
+
+        if( mNewRecord< 0 )
+          {
+          mNewRecord = -mNewRecord;
+          mIsNewRecord = false;
+          }
+        else
+          {
+          mIsNewRecord = true;
+          }
+        }
+
+android.util.Log.e("post", "1 canDrag true");
+
+      mCanDrag   = true;
+      mCanRotate = false;
+      mCanUI     = false;
+      doEffectNow( BaseEffect.Type.WIN );
+      }
+    else
+      {
+      mCanRotate = true;
+      mCanUI     = true;
+      }
+
+    mIsSolved = solved;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void removeRotation()
+    {
+    mRemoveRotation = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addRotationNow()
+    {
+    mAddRotation = false;
+
+    mAddRotationID = mNewObject.addNewRotation( mAddRotationAxis, mAddRotationRowBitmap,
+                                                mAddRotationAngle, mAddRotationDuration, this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void finishRotationNow()
+    {
+    mFinishRotation = false;
+    mCanRotate      = false;
+    mCanUI          = false;
+    mRotationFinishedID = mNewObject.finishRotationNow(this);
+
+    if( mRotationFinishedID==0 ) // failed to add effect - should never happen
+      {
+      mCanRotate = true;
+      mCanUI     = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void changeObjectNow()
+    {
+    mChangeObject = false;
+
+    if ( mNewObject==null || mNewObject.getObjectList()!=mNextObject || mNewObject.getSize()!=mNextSize)
+      {
+      mCanDrag      = false;
+      mCanRotate    = false;
+      mCanUI        = false;
+      createObjectNow(mNextObject, mNextSize, mNextMoves);
+      doEffectNow( BaseEffect.Type.SIZECHANGE );
+      }
+    else
+      {
+      mNewObject.initializeObject(mNextMoves);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void scrambleObjectNow()
+    {
+    mScrambleObject = false;
+    mCanDrag        = false;
+    mCanRotate      = false;
+    mCanUI          = false;
+    mIsSolved       = false;
+    RubikScores.getInstance().incrementNumPlays();
+    doEffectNow( BaseEffect.Type.SCRAMBLE );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void solveObjectNow()
+    {
+    mSolveObject    = false;
+    mCanDrag        = false;
+    mCanRotate      = false;
+    mCanUI          = false;
+    doEffectNow( BaseEffect.Type.SOLVE );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setQuatCurrentNow()
+    {
+    mSetQuatCurrent = false;
+    mView.setQuatCurrent();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setQuatAccumulatedNow()
+    {
+    mSetQuatAccumulated = false;
+    mView.setQuatAccumulated();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setScreenSize(int width, int height)
+    {
+    if( mNewObject!=null )
+      {
+      mNewObject.createTexture();
+      mNewObject.recomputeScaleFactor(width,height);
+      }
+
+    mScreenHeight = height;
+    mScreenWidth  = width;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void savePreferences(SharedPreferences.Editor editor)
+    {
+    if( mNewObject!=null )
+      {
+      mNewObject.savePreferences(editor);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void restorePreferences(SharedPreferences preferences)
+    {
+    mPreferences = preferences;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void finishRotation()
+    {
+    mFinishRotation = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void changeObject(RubikObjectList object, int size, String moves)
+    {
+    if( size>0 )
+      {
+      mChangeObject = true;
+      mNextObject = object;
+      mNextSize   = size;
+      mNextMoves  = moves;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void scrambleObject(int num)
+    {
+    if( mCanUI )
+      {
+      mScrambleObject = true;
+      mScrambleObjectNum = num;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void solveObject()
+    {
+    if( mCanUI )
+      {
+      mSolveObject = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean canRotate()
+    {
+    return mCanRotate;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean canDrag()
+    {
+    return mCanDrag;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setQuatCurrentOnNextRender()
+    {
+    mSetQuatCurrent = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setQuatAccumulatedOnNextRender()
+    {
+    mSetQuatAccumulated = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void postRender()
+    {
+    if( mSetQuatCurrent     ) setQuatCurrentNow();
+    if( mSetQuatAccumulated ) setQuatAccumulatedNow();
+    if( mFinishRotation     ) finishRotationNow();
+    if( mRemoveRotation     ) removeRotationNow();
+    if( mChangeObject       ) changeObjectNow();
+    if( mSolveObject        ) solveObjectNow();
+    if( mScrambleObject     ) scrambleObjectNow();
+    if( mAddRotation        ) addRotationNow();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void addRotation(ActionFinishedListener listener, int axis, int rowBitmap, int angle, long duration)
+    {
+    mAddRotation = true;
+
+    mAddActionListener    = listener;
+    mAddRotationAxis      = axis;
+    mAddRotationRowBitmap = rowBitmap;
+    mAddRotationAngle     = angle;
+    mAddRotationDuration  = duration;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikObject getObject()
+    {
+    return mNewObject;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikObject getOldObject()
+    {
+    return mOldObject;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumScrambles()
+    {
+    return mScrambleObjectNum;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
+    if( effectID == mRotationFinishedID )
+      {
+      mRotationFinishedID = 0;
+      removeRotation();
+      }
+    else if( effectID == mAddRotationID )
+      {
+      mAddRotationID = 0;
+      mAddActionListener.onActionFinished(effectID);
+      }
+    else
+      {
+      for(int i=0; i<BaseEffect.Type.LENGTH; i++)
+        {
+        if( effectID == mEffectID[i] )
+          {
+          mCanRotate   = true;
+          mCanDrag     = true;
+          mCanUI       = true;
+
+          if( i==BaseEffect.Type.SCRAMBLE.ordinal() )
+            {
+            final RubikActivity act = (RubikActivity)mView.getContext();
+
+            act.runOnUiThread(new Runnable()
+              {
+              @Override
+              public void run()
+                {
+                RubikState.switchState( act, RubikState.SOLV);
+                }
+              });
+            }
+
+          if( i==BaseEffect.Type.WIN.ordinal() )
+            {
+            if( RubikState.getCurrentState()==RubikState.SOLV )
+              {
+              final RubikActivity act = (RubikActivity)mView.getContext();
+              Bundle bundle = new Bundle();
+              bundle.putLong("time", mNewRecord );
+
+              if( mIsNewRecord )
+                {
+                RubikDialogNewRecord dialog = new RubikDialogNewRecord();
+                dialog.setArguments(bundle);
+                dialog.show( act.getSupportFragmentManager(), null);
+                }
+              else
+                {
+                RubikDialogSolved dialog = new RubikDialogSolved();
+                dialog.setArguments(bundle);
+                dialog.show( act.getSupportFragmentManager(), null);
+                }
+              }
+            }
+
+          break;
+          }
+        }
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index de4e72b6..3867c5ec 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -19,207 +19,30 @@
 
 package org.distorted.magic;
 
-import android.content.SharedPreferences;
 import android.opengl.GLSurfaceView;
-import android.os.Bundle;
-
-import org.distorted.dialog.RubikDialogNewRecord;
-import org.distorted.dialog.RubikDialogSolved;
 import org.distorted.effect.BaseEffect;
 import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.message.EffectListener;
-import org.distorted.object.RubikObject;
-import org.distorted.object.RubikObjectList;
-import org.distorted.scores.RubikScores;
-import org.distorted.uistate.RubikState;
-import org.distorted.uistate.RubikStateSolving;
 
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
+public class RubikRenderer implements GLSurfaceView.Renderer
 {
-    public static final int NODE_FBO_SIZE = 600;
-
-    private RubikSurfaceView mView;
-    private DistortedScreen mScreen;
-    private RubikObjectList mNextObject;
-    private int mNextSize;
-    private int mScrambleObjectNum;
-    private long mRotationFinishedID;
-    private long[] mEffectID;
-    private boolean mFinishRotation, mRemoveRotation, mSetQuatCurrent, mSetQuatAccumulated;
-    private boolean mChangeObject, mSolveObject, mScrambleObject;
-    private boolean mCanRotate, mCanDrag, mCanUI;
-    private boolean mIsSolved;
-    private boolean mIsNewRecord;
-    private long mNewRecord;
-    private RubikObject mOldObject, mNewObject;
-    private int mScreenWidth, mScreenHeight;
-    private SharedPreferences mPreferences;
-    private String mNextMoves;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    RubikRenderer(RubikSurfaceView v)
-      {
-      mView = v;
-      mScreen = new DistortedScreen();
-
-      mOldObject = null;
-      mNewObject = null;
-
-      mScreenWidth = mScreenHeight = 0;
-      mScrambleObjectNum = 0;
-
-      mFinishRotation     = false;
-      mRemoveRotation     = false;
-      mSetQuatCurrent     = false;
-      mSetQuatAccumulated = false;
-      mChangeObject       = false;
-      mSolveObject        = false;
-      mScrambleObject     = false;
-
-      mCanRotate   = true;
-      mCanDrag     = true;
-      mCanUI       = true;
-
-      mEffectID = new long[BaseEffect.Type.LENGTH];
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void createObjectNow(RubikObjectList object, int size, String moves)
-     {
-     boolean firstTime = (mNewObject==null);
-
-     if( mOldObject!=null ) mOldObject.releaseResources();
-     mOldObject = mNewObject;
-
-     mNewObject = object.create(size, mView.getQuatCurrent(), mView.getQuatAccumulated(), moves);
-     mNewObject.createTexture();
-     mView.setMovement(object.getObjectMovementClass());
-
-     if( firstTime ) mNewObject.restorePreferences(mPreferences);
-
-     if( mScreenWidth!=0 )
-       {
-       mNewObject.recomputeScaleFactor(mScreenWidth, mScreenHeight);
-       }
-
-     mIsSolved = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// do all 'adjustable' effects (SizeChange, Solve, Scramble)
-
-   private void doEffectNow(BaseEffect.Type type)
-     {
-     int index = type.ordinal();
-
-     try
-       {
-       mEffectID[index] = type.startEffect(this);
-       }
-     catch( Exception ex )
-       {
-       android.util.Log.e("renderer", "exception starting effect: "+ex.getMessage());
-
-       mCanUI     = true;
-       mCanRotate = true;
-       mCanDrag   = true;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void savePreferences(SharedPreferences.Editor editor)
-     {
-     if( mNewObject!=null )
-       {
-       mNewObject.savePreferences(editor);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void restorePreferences(SharedPreferences preferences)
-     {
-     mPreferences = preferences;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// no this will not race with onDrawFrame
-
-   void finishRotation()
-     {
-     mFinishRotation = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void changeObject(RubikObjectList object, int size, String moves)
-     {
-     if( size>0 )
-       {
-       mChangeObject = true;
-       mNextObject = object;
-       mNextSize   = size;
-       mNextMoves  = moves;
-       }
-     }
+   public static final int NODE_FBO_SIZE = 600;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void scrambleObject(int num)
-     {
-     if( mCanUI )
-       {
-       mScrambleObject = true;
-       mScrambleObjectNum = num;
-       }
-     }
+   private RubikSurfaceView mView;
+   private DistortedScreen mScreen;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   void solveObject()
+   RubikRenderer(RubikSurfaceView v)
      {
-     if( mCanUI )
-       {
-       mSolveObject = true;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   boolean canRotate()
-     {
-     return mCanRotate;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   boolean canDrag()
-     {
-     return mCanDrag;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setQuatCurrentOnNextRender()
-     {
-     mSetQuatCurrent = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setQuatAccumulatedOnNextRender()
-     {
-     mSetQuatAccumulated = true;
+     mView = v;
+     mScreen = new DistortedScreen();
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -230,120 +53,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
    public void onDrawFrame(GL10 glUnused)
      {
      mScreen.render( System.currentTimeMillis() );
-
-     if( mSetQuatCurrent )
-       {
-       mSetQuatCurrent = false;
-       mView.setQuatCurrent();
-       }
-
-     if( mSetQuatAccumulated )
-       {
-       mSetQuatAccumulated = false;
-       mView.setQuatAccumulated();
-       }
-
-     if( mFinishRotation )
-       {
-       mFinishRotation = false;
-       mCanRotate      = false;
-       mCanUI          = false;
-       mRotationFinishedID = mNewObject.finishRotationNow(this);
-
-       if( mRotationFinishedID==0 ) // failed to add effect - should never happen
-         {
-         mCanRotate = true;
-         mCanUI     = true;
-         }
-       }
-
-     if( mRemoveRotation )
-       {
-       mRemoveRotation=false;
-       mNewObject.removeRotationNow();
-
-       boolean solved = mNewObject.isSolved();
-
-       if( solved && !mIsSolved )
-         {
-         if( RubikState.getCurrentState()==RubikState.SOLV )
-           {
-           RubikStateSolving solving = (RubikStateSolving)RubikState.SOLV.getStateClass();
-           mNewRecord = solving.stopCounting((RubikActivity)mView.getContext());
-
-           if( mNewRecord< 0 )
-             {
-             mNewRecord = -mNewRecord;
-             mIsNewRecord = false;
-             }
-           else
-             {
-             mIsNewRecord = true;
-             }
-           }
-
-         mCanDrag   = true;
-         mCanRotate = false;
-         mCanUI     = false;
-         doEffectNow( BaseEffect.Type.WIN );
-         }
-       else
-         {
-         mCanRotate = true;
-         mCanUI     = true;
-         }
-
-       mIsSolved = solved;
-       }
-
-     if( mChangeObject )
-       {
-       mChangeObject = false;
-       mCanDrag      = false;
-       mCanRotate    = false;
-       mCanUI        = false;
-
-       if( mNewObject==null )
-         {
-         createObjectNow(mNextObject, mNextSize, mNextMoves);
-         doEffectNow( BaseEffect.Type.SIZECHANGE );
-         }
-       else
-         {
-         RubikObjectList list = mNewObject.getObjectList();
-         int size = mNewObject.getSize();
-
-         if (list!=mNextObject || mNextSize!=size)
-           {
-           createObjectNow(mNextObject, mNextSize, mNextMoves);
-           doEffectNow( BaseEffect.Type.SIZECHANGE );
-           }
-         else
-           {
-           mNewObject.initializeObject(mNextMoves);
-           }
-         }
-       }
-
-     if( mSolveObject )
-       {
-       mSolveObject    = false;
-       mCanDrag        = false;
-       mCanRotate      = false;
-       mCanUI          = false;
-       doEffectNow( BaseEffect.Type.SOLVE );
-       }
-
-     if( mScrambleObject )
-       {
-       mScrambleObject = false;
-       mCanDrag        = false;
-       mCanRotate      = false;
-       mCanUI          = false;
-       mIsSolved       = false;
-       RubikScores.getInstance().incrementNumPlays();
-       doEffectNow( BaseEffect.Type.SCRAMBLE );
-       }
+     mView.getPostRender().postRender();
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -351,18 +61,9 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height)
       {
-      if( mNewObject!=null ) mNewObject.createTexture();
-
-      mScreen.resize(width, height);
+      mScreen.resize(width,height);
       mView.setScreenSize(width,height);
-
-      if( mNewObject!=null )
-        {
-        mNewObject.recomputeScaleFactor(width,height);
-        }
-
-      mScreenHeight = height;
-      mScreenWidth  = width;
+      mView.getPostRender().setScreenSize(width,height);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -383,94 +84,10 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
         }
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public void effectFinished(final long effectID)
-     {
-     if( effectID == mRotationFinishedID )
-       {
-       mRemoveRotation = true;
-       }
-     else
-       {
-       for(int i=0; i<BaseEffect.Type.LENGTH; i++)
-         {
-         if( effectID == mEffectID[i] )
-           {
-           mCanRotate   = true;
-           mCanDrag     = true;
-           mCanUI       = true;
-
-           if( i==BaseEffect.Type.SCRAMBLE.ordinal() )
-             {
-             final RubikActivity act = (RubikActivity)mView.getContext();
-
-             act.runOnUiThread(new Runnable()
-               {
-               @Override
-               public void run()
-                 {
-                 RubikState.switchState( act, RubikState.SOLV);
-                 }
-               });
-             }
-
-           if( i==BaseEffect.Type.WIN.ordinal() )
-             {
-             if( RubikState.getCurrentState()==RubikState.SOLV )
-               {
-               final RubikActivity act = (RubikActivity)mView.getContext();
-               Bundle bundle = new Bundle();
-               bundle.putLong("time", mNewRecord );
-
-               if( mIsNewRecord )
-                 {
-                 RubikDialogNewRecord dialog = new RubikDialogNewRecord();
-                 dialog.setArguments(bundle);
-                 dialog.show( act.getSupportFragmentManager(), null);
-                 }
-               else
-                 {
-                 RubikDialogSolved dialog = new RubikDialogSolved();
-                 dialog.setArguments(bundle);
-                 dialog.show( act.getSupportFragmentManager(), null);
-                 }
-               }
-             }
-
-           break;
-           }
-         }
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public RubikObject getObject()
-     {
-     return mNewObject;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public RubikObject getOldObject()
-     {
-     return mOldObject;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public DistortedScreen getScreen()
+   DistortedScreen getScreen()
      {
      return mScreen;
      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public int getNumScrambles()
-     {
-     return mScrambleObjectNum;
-     }
 }
diff --git a/src/main/java/org/distorted/magic/RubikSurfaceView.java b/src/main/java/org/distorted/magic/RubikSurfaceView.java
index 2bb0286b..c8a50e37 100644
--- a/src/main/java/org/distorted/magic/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/magic/RubikSurfaceView.java
@@ -58,6 +58,7 @@ public class RubikSurfaceView extends GLSurfaceView
     private final Static4D CAMERA_POINT = new Static4D(0, 0, (float)Math.sqrt(3)*0.5f, 0);
 
     private RubikRenderer mRenderer;
+    private RubikPostRender mPostRender;
     private RubikObjectMovement mMovement;
     private boolean mDragging, mBeginningRotation, mContinuingRotation;
     private int mScreenWidth, mScreenHeight, mScreenMin;
@@ -96,6 +97,13 @@ public class RubikSurfaceView extends GLSurfaceView
       return mRenderer;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    RubikPostRender getPostRender()
+      {
+      return mPostRender;
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     void setQuatAccumulated()
@@ -177,12 +185,12 @@ public class RubikSurfaceView extends GLSurfaceView
         if( mMovement!=null && mMovement.faceTouched(rotatedTouchPoint1,rotatedCamera) )
           {
           mDragging           = false;
-          mBeginningRotation  = mRenderer.canRotate();
+          mBeginningRotation  = mPostRender.canRotate();
           mContinuingRotation = false;
           }
         else
           {
-          mDragging           = mRenderer.canDrag();
+          mDragging           = mPostRender.canDrag();
           mBeginningRotation  = false;
           mContinuingRotation = false;
           }
@@ -288,7 +296,8 @@ public class RubikSurfaceView extends GLSurfaceView
 
       if(!isInEditMode())
         {
-        mRenderer = new RubikRenderer(this);
+        mRenderer   = new RubikRenderer(this);
+        mPostRender = new RubikPostRender(this);
 
         final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
@@ -323,7 +332,7 @@ public class RubikSurfaceView extends GLSurfaceView
                                            Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuatAccumulated);
 
                                            Static2D res = mMovement.newRotation(rotatedTouchPoint2);
-                                           RubikObject object = mRenderer.getObject();
+                                           RubikObject object = mPostRender.getObject();
 
                                            int axis = (int)res.get0();
                                            float offset = res.get1();
@@ -345,12 +354,12 @@ public class RubikSurfaceView extends GLSurfaceView
                                        else if( mContinuingRotation )
                                          {
                                          float angle = continueRotation(x-mStartRotX,y-mStartRotY);
-                                         mRenderer.getObject().continueRotation(SWIPING_SENSITIVITY*angle);
+                                         mPostRender.getObject().continueRotation(SWIPING_SENSITIVITY*angle);
                                          }
                                        else if( mDragging )
                                          {
                                          mTempCurrent.set(quatFromDrag(mX-x,y-mY));
-                                         mRenderer.setQuatCurrentOnNextRender();
+                                         mPostRender.setQuatCurrentOnNextRender();
 
                                          if( (mX-x)*(mX-x) + (mY-y)*(mY-y) > 1.0f/(DIRECTION_SENSITIVITY*DIRECTION_SENSITIVITY) )
                                            {
@@ -358,11 +367,11 @@ public class RubikSurfaceView extends GLSurfaceView
                                            mY = y;
                                            mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
                                            mTempCurrent.set(0f, 0f, 0f, 1f);
-                                           mRenderer.setQuatCurrentOnNextRender();
-                                           mRenderer.setQuatAccumulatedOnNextRender();
+                                           mPostRender.setQuatCurrentOnNextRender();
+                                           mPostRender.setQuatAccumulatedOnNextRender();
                                            }
                                          }
-                                       else if( mRenderer.canRotate() || mRenderer.canDrag() )
+                                       else if( mPostRender.canRotate() || mPostRender.canDrag() )
                                          {
                                          setUpDragOrRotate(x,y);
                                          }
@@ -371,13 +380,13 @@ public class RubikSurfaceView extends GLSurfaceView
                                          {
                                          mTempAccumulated.set(quatMultiply(mQuatCurrent, mQuatAccumulated));
                                          mTempCurrent.set(0f, 0f, 0f, 1f);
-                                         mRenderer.setQuatCurrentOnNextRender();
-                                         mRenderer.setQuatAccumulatedOnNextRender();
+                                         mPostRender.setQuatCurrentOnNextRender();
+                                         mPostRender.setQuatAccumulatedOnNextRender();
                                          }
 
                                        if( mContinuingRotation )
                                          {
-                                         mRenderer.finishRotation();
+                                         mPostRender.finishRotation();
                                          }
                                        break;
          }
diff --git a/src/main/java/org/distorted/object/RubikObject.java b/src/main/java/org/distorted/object/RubikObject.java
index c861052b..cc175166 100644
--- a/src/main/java/org/distorted/object/RubikObject.java
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -220,56 +220,6 @@ public abstract class RubikObject extends DistortedNode
     rotationAngle.add(mRotationAngleFinal);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
-// rotV : 0 --> VECTX, 1--> VECTY, 2 --> VECTZ
-// rotRC: 01010 --> move the 2nd and 4th layer
-// rotA : counterclockwise by rotA*90 degrees
-
-	private boolean scheduleMultiRotation(int rotV, int rotRC, int rotA)
-	  {
-	  /*
-		int index, tmp;
-
-		if( numRotations>0 )
-		  {
-			if( rotVector!=rotV ) return false;
-
-			index=0;
-			tmp=rotRC;
-
-			while( tmp!=0 )
-			  {
-				if( (tmp&0x1)==1 && rotAngle[index]!=0 ) return false;
-				index++;
-				tmp/=2;
-			  }
-		  }
-
-		index=0;
-		tmp=rotRC;
-		rotVector = rotV;
-
-		while( tmp!=0 )
-		  {
-			if( (tmp&0x1)==1 )
-			  {
-				rotAngle[index] = rotA*90;
-				rotSpeed[index] = (int)(rotS/RubikWorld.hardwareSpeed);
-
-				if( rotSpeed[index]<=0 && rotS>0 ) rotSpeed[index] = 1;
-				if( rotSpeed[index]>=0 && rotS<0 ) rotSpeed[index] =-1;
-
-				numRotations++;
-			  }
-
-			index++;
-			tmp/=2;
-		  }
-    */
-		return true;
-	  }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // TODO
 
@@ -571,36 +521,6 @@ public abstract class RubikObject extends DistortedNode
      mRotationAngleStatic.set0(0);
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean makeMove(String move)
-    {
-    int a1=move.charAt(1)-'0';
-		int a2=move.charAt(2)-'0';
-		int a3=move.charAt(3)-'0';
-
-    int rotVector = (10*a1+a2)/32;
-    int rotBitmap = (10*a1+a2)%32;
-    int rotAngle  = 2-a3;
-
-		return scheduleMultiRotation(rotVector, rotBitmap, rotAngle);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean backMove(String move)
-    {
-    int a1=move.charAt(1)-'0';
-		int a2=move.charAt(2)-'0';
-		int a3=move.charAt(3)-'0';
-
-    int rotVector = (10*a1+a2)/32;
-    int rotBitmap = (10*a1+a2)%32;
-    int rotAngle  = a3-2;
-
-		return scheduleMultiRotation(rotVector, rotBitmap, rotAngle);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void initializeObject(String moves)
diff --git a/src/main/java/org/distorted/patterns/RubikPattern.java b/src/main/java/org/distorted/patterns/RubikPattern.java
index 01b9c453..3a3979e9 100644
--- a/src/main/java/org/distorted/patterns/RubikPattern.java
+++ b/src/main/java/org/distorted/patterns/RubikPattern.java
@@ -19,6 +19,7 @@
 
 package org.distorted.patterns;
 
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObject;
 
 import java.util.Vector;
@@ -27,6 +28,8 @@ import java.util.Vector;
 
 public class RubikPattern
 {
+  private static final int DURATION_MILLIS = 1000;
+
   public static final int MIN_CUBE  = 2;
   public static final int MAX_CUBE  = 5;
   public static final int NUM_CUBES = MAX_CUBE-MIN_CUBE+1;
@@ -113,23 +116,23 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void makeMove(RubikObject object, int pattern)
+    void makeMove(RubikPostRender post, int pattern)
       {
       if( pattern>=0 && pattern<numPatterns )
         {
         Pattern p = patterns.elementAt(pattern);
-        if( p!=null ) p.makeMove(object);
+        if( p!=null ) p.makeMove(post);
         }
       }
 
   /////////////////////////////////////////////////////////////
 
-    void backMove(RubikObject object, int pattern)
+    void backMove(RubikPostRender post, int pattern)
       {
       if( pattern>=0 && pattern<numPatterns )
         {
         Pattern p = patterns.elementAt(pattern);
-        if( p!=null ) p.backMove(object);
+        if( p!=null ) p.backMove(post);
         }
       }
 
@@ -149,13 +152,16 @@ public class RubikPattern
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private static class Pattern
+  private static class Pattern implements RubikPostRender.ActionFinishedListener
     {
     private String name;
     private String moves;
     private int curMove;
     private int numMove;
 
+    private boolean mCanRotate;
+    private RubikObject mObject;
+
   /////////////////////////////////////////////////////////////
 
     Pattern(String n, String m)
@@ -164,6 +170,8 @@ public class RubikPattern
       moves=m;
       numMove = moves.length()/4;
       curMove=numMove;
+
+      mCanRotate = true;
       }
 
   /////////////////////////////////////////////////////////////
@@ -176,32 +184,52 @@ public class RubikPattern
   /////////////////////////////////////////////////////////////
 
     int getNumMove()
-    {
-    return numMove;
-    }
+      {
+      return numMove;
+      }
 
   /////////////////////////////////////////////////////////////
 
     int getCurMove()
-    {
-    return curMove;
-    }
+      {
+      return curMove;
+      }
 
   /////////////////////////////////////////////////////////////
 
-    void makeMove(RubikObject object)
+    void makeMove(RubikPostRender post)
       {
       curMove++;
+      RubikObject object = post.getObject();
 
-      if( curMove>numMove)
+      if( curMove>numMove )
         {
         curMove= 0;
         object.initializeObject(moves.substring(0,4*curMove));
         }
       else
         {
-        if( !object.makeMove(moves.substring(4*curMove-4,4*curMove)) )
+        if( mCanRotate )
           {
+          mCanRotate = false;
+          mObject = object;
+
+          String move = moves.substring(4*curMove-4,4*curMove);
+          int a1=move.charAt(1)-'0';
+		      int a2=move.charAt(2)-'0';
+		      int a3=move.charAt(3)-'0';
+
+          int axis      = (10*a1+a2)/32;
+          int rowBitmap = (10*a1+a2)%32;
+          int angle     = (2-a3)*(360/object.getBasicAngle());
+          int numRot    = Math.abs(2-a3);
+
+          post.addRotation(this, axis, rowBitmap, angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("pattern", "failed to make Move!");
+
           curMove--;
           }
         }
@@ -209,19 +237,38 @@ public class RubikPattern
 
   /////////////////////////////////////////////////////////////
 
-    void backMove(RubikObject object)
+    void backMove(RubikPostRender post)
       {
       curMove--;
+      RubikObject object = post.getObject();
 
-      if( curMove<0)
+      if( curMove<0 )
         {
         curMove=numMove;
         object.initializeObject(moves.substring(0,4*curMove));
         }
       else
         {
-        if( !object.backMove(moves.substring(4*curMove,4*curMove+4)) )
+        if( mCanRotate )
           {
+          mCanRotate = false;
+          mObject = object;
+
+          String move = moves.substring(4*curMove,4*curMove+4);
+          int a1=move.charAt(1)-'0';
+		      int a2=move.charAt(2)-'0';
+		      int a3=move.charAt(3)-'0';
+
+          int axis      = (10*a1+a2)/32;
+          int rowBitmap = (10*a1+a2)%32;
+          int angle     = (2-a3)*(360/object.getBasicAngle());
+          int numRot    = Math.abs(2-a3);
+
+          post.addRotation(this, axis, rowBitmap, -angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("pattern", "failed to back Move!");
           curMove++;
           }
         }
@@ -233,6 +280,14 @@ public class RubikPattern
       {
       return moves.substring(0,4*curMove);
       }
+
+  /////////////////////////////////////////////////////////////
+
+    public void onActionFinished(final long effectID)
+      {
+      mObject.removeRotationNow();
+      mCanRotate = true;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -384,23 +439,23 @@ public class RubikPattern
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void makeMove(RubikObject object, int size, int cat, int pat)
+  public void makeMove(RubikPostRender post, int size, int cat, int pat)
     {
     if( size>=0 && size<NUM_CUBES && cat>=0 && cat< numCategories[size] )
       {
       Category c = mCategories[size].elementAt(cat);
-      if( c!=null ) c.makeMove(object,pat);
+      if( c!=null ) c.makeMove(post,pat);
       }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void backMove(RubikObject object, int size, int cat, int pat)
+  public void backMove(RubikPostRender post, int size, int cat, int pat)
     {
     if( size>=0 && size<NUM_CUBES && cat>=0 && cat< numCategories[size] )
       {
       Category c = mCategories[size].elementAt(cat);
-      if( c!=null ) c.backMove(object,pat);
+      if( c!=null ) c.backMove(post,pat);
       }
     }
 
diff --git a/src/main/java/org/distorted/uistate/RubikStatePattern.java b/src/main/java/org/distorted/uistate/RubikStatePattern.java
index ce95d3d3..47fe3f8a 100644
--- a/src/main/java/org/distorted/uistate/RubikStatePattern.java
+++ b/src/main/java/org/distorted/uistate/RubikStatePattern.java
@@ -34,7 +34,7 @@ import android.widget.TextView;
 import org.distorted.dialog.RubikDialogPattern;
 import org.distorted.magic.R;
 import org.distorted.magic.RubikActivity;
-import org.distorted.object.RubikObject;
+import org.distorted.magic.RubikPostRender;
 import org.distorted.object.RubikObjectList;
 import org.distorted.patterns.RubikPattern;
 
@@ -49,7 +49,7 @@ public class RubikStatePattern extends RubikStateAbstract
   private Button mBackButton;
   private ImageButton mPrevButton, mNextButton;
   private TextView mMovesText;
-  private int mNumMoves, mCurrMove;
+  private int mNumMoves;
   private int mSizeIndex, mCategory, mPattern;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -192,11 +192,11 @@ public class RubikStatePattern extends RubikStateAbstract
       @Override
       public void onClick(View v)
         {
-        if( --mCurrMove< 0 ) mCurrMove=mNumMoves;
-
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-        RubikObject object = act.getObject();
-        RubikPattern.getInstance().backMove( object, mSizeIndex, mCategory, mPattern);
+        RubikPattern pattern = RubikPattern.getInstance();
+        RubikPostRender post = act.getPostRender();
+        pattern.backMove( post, mSizeIndex, mCategory, mPattern);
+        int currMove = pattern.getCurMove(mSizeIndex, mCategory, mPattern);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
         }
       });
     }
@@ -217,11 +217,11 @@ public class RubikStatePattern extends RubikStateAbstract
       @Override
       public void onClick(View v)
         {
-        if( ++mCurrMove> mNumMoves ) mCurrMove=0;
-
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-        RubikObject object = act.getObject();
-        RubikPattern.getInstance().makeMove( object, mSizeIndex, mCategory, mPattern);
+        RubikPattern pattern = RubikPattern.getInstance();
+        RubikPostRender post = act.getPostRender();
+        pattern.makeMove( post, mSizeIndex, mCategory, mPattern);
+        int currMove = pattern.getCurMove(mSizeIndex, mCategory, mPattern);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
         }
       });
     }
@@ -239,7 +239,9 @@ public class RubikStatePattern extends RubikStateAbstract
     mMovesText.setPadding(padding,0,padding,0);
     mMovesText.setGravity(Gravity.CENTER);
 
-    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    RubikPattern pattern = RubikPattern.getInstance();
+    int currMove = pattern.getCurMove(mSizeIndex, mCategory, mPattern);
+    mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -254,10 +256,10 @@ public class RubikStatePattern extends RubikStateAbstract
     String patternName = patt.getPatternName(sizeIndex,category,pattern);
     mText.setText(patternName);
 
-    mNumMoves = patt.getNumMoves(sizeIndex,category,pattern);
-    mCurrMove = patt.getCurMove(sizeIndex,category,pattern);
+    mNumMoves   = patt.getNumMoves(sizeIndex,category,pattern);
+    int currMove= patt.getCurMove(sizeIndex,category,pattern);
 
-    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
