commit 27a70eaea62eefa2f064cafd0e80989cc1a75a7c
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Feb 21 11:00:56 2020 +0000

    Make RubikCube and RubikCubeMovement generic and not visible outside of their package.

diff --git a/src/main/java/org/distorted/dialog/RubikDialogScores.java b/src/main/java/org/distorted/dialog/RubikDialogScores.java
index 9ba39a2f..f0312f44 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScores.java
@@ -36,7 +36,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.distorted.magic.R;
-import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -104,10 +104,10 @@ public class RubikDialogScores extends AppCompatDialogFragment
 
     viewPager.setCurrentItem(curTab);
 
-    for (int i = 0; i< RubikObject.LENGTH; i++)
+    for (int i = 0; i< RubikObjectList.LENGTH; i++)
       {
       ImageView imageView = new ImageView(act);
-      imageView.setImageResource(RubikObject.getObject(i).getIconID());
+      imageView.setImageResource(RubikObjectList.getObject(i).getIconID());
       TabLayout.Tab tab = tabLayout.getTabAt(i);
       if(tab!=null) tab.setCustomView(imageView);
       }
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
index aa3cc1e5..790eab4f 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
@@ -28,7 +28,7 @@ import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import org.distorted.network.RubikScoresDownloader;
-import static org.distorted.object.RubikObject.LENGTH;
+import static org.distorted.object.RubikObjectList.LENGTH;
 import static org.distorted.uistate.RubikStatePlay.MAX_SCRAMBLE;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
index d80b570d..610738b7 100644
--- a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
@@ -23,12 +23,16 @@ import org.distorted.effect.BaseEffect;
 import org.distorted.library.effect.Effect;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.message.EffectListener;
-import org.distorted.object.RubikCube;
 import org.distorted.magic.RubikRenderer;
+import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
 import java.util.Random;
 
+import static org.distorted.object.RubikObjectList.VECTX;
+import static org.distorted.object.RubikObjectList.VECTY;
+import static org.distorted.object.RubikObjectList.VECTZ;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public abstract class ScrambleEffect extends BaseEffect implements EffectListener
@@ -69,7 +73,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
   private int mLastVector;
   private long mDurationSingleTurn;
   private Random mRnd;
-  private RubikCube mCube;
+  private RubikObject mObject;
 
   Effect[] mNodeEffects;
   int[] mNodeEffectPosition;
@@ -123,9 +127,9 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
         {
         switch(mRnd.nextInt(3))
           {
-          case 0: mLastVector = RubikCube.VECTX; break;
-          case 1: mLastVector = RubikCube.VECTY; break;
-          case 2: mLastVector = RubikCube.VECTZ; break;
+          case 0: mLastVector = VECTX; break;
+          case 1: mLastVector = VECTY; break;
+          case 2: mLastVector = VECTZ; break;
           }
         }
       else
@@ -134,13 +138,13 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
 
         switch(mLastVector)
           {
-          case RubikCube.VECTX: mLastVector = (newVector==0 ? RubikCube.VECTY: RubikCube.VECTZ); break;
-          case RubikCube.VECTY: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTZ); break;
-          case RubikCube.VECTZ: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTY); break;
+          case VECTX: mLastVector = (newVector==0 ? VECTY: VECTZ); break;
+          case VECTY: mLastVector = (newVector==0 ? VECTX: VECTZ); break;
+          case VECTZ: mLastVector = (newVector==0 ? VECTX: VECTY); break;
           }
         }
 
-      int row  = mRnd.nextInt(mCube.getSize());
+      int row  = mRnd.nextInt(mObject.getSize());
       int angle= randomizeAngle();
       int absAngle = (angle<0 ? -angle : angle);
       long durationMillis =  absAngle*mDurationSingleTurn;
@@ -153,7 +157,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
         android.util.Log.e("effect", "ERROR: "+mNumDoubleScramblesLeft);
         }
 
-      mCurrentBaseEffectID = mCube.addNewRotation(mLastVector, row, angle*90, durationMillis, this );
+      mCurrentBaseEffectID = mObject.addNewRotation(mLastVector, row, angle*90, durationMillis, this );
       }
     else
       {
@@ -183,11 +187,11 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     {
     for(int i=0; i<mCubeEffectNumber; i++)
       {
-      mCube.apply(mCubeEffects[i],mCubeEffectPosition[i]);
+      mObject.apply(mCubeEffects[i],mCubeEffectPosition[i]);
       mCubeEffects[i].notifyWhenFinished(this);
       }
 
-    DistortedEffects nodeEffects = mCube.getEffects();
+    DistortedEffects nodeEffects = mObject.getEffects();
 
     for(int i=0; i<mNodeEffectNumber; i++)
       {
@@ -202,10 +206,10 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     {
     for(int i=0; i<mCubeEffectNumber; i++)
       {
-      mCube.remove(mCubeEffects[i].getID());
+      mObject.remove(mCubeEffects[i].getID());
       }
 
-    DistortedEffects nodeEffects = mCube.getEffects();
+    DistortedEffects nodeEffects = mObject.getEffects();
 
     for(int i=0; i<mNodeEffectNumber; i++)
       {
@@ -244,7 +248,7 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
     {
     if( effectID == mCurrentBaseEffectID )
       {
-      mCube.removeRotationNow();
+      mObject.removeRotationNow();
       addNewScramble();
       return;
       }
@@ -301,10 +305,10 @@ public abstract class ScrambleEffect extends BaseEffect implements EffectListene
   @SuppressWarnings("unused")
   public long start(int duration, RubikRenderer renderer)
     {
-    mCube     = renderer.getCube();
+    mObject   = renderer.getObject();
     mListener = renderer;
 
-    mCube.solve();
+    mObject.solve();
 
     int numScrambles = renderer.getNumScrambles();
     int dura = (int)(duration*Math.pow(numScrambles,0.6f));
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
index 0b704956..884e2a64 100644
--- a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
@@ -24,8 +24,8 @@ 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.object.RubikCube;
 import org.distorted.magic.RubikRenderer;
+import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
 
@@ -72,7 +72,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
   private int[] mCubeEffectNumber, mNodeEffectNumber;
   private int[] mEffectFinished;
   private boolean[] mPhaseActive;
-  private RubikCube[] mCube;
+  private RubikObject[] mObject;
 
   DistortedScreen mScreen;
   Effect[][] mCubeEffects;
@@ -93,7 +93,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
     mNodeEffectPosition = new int[NUM_PHASES][];
     mCubeEffects        = new Effect[NUM_PHASES][];
     mNodeEffects        = new Effect[NUM_PHASES][];
-    mCube               = new RubikCube[NUM_PHASES];
+    mObject             = new RubikObject[NUM_PHASES];
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -112,7 +112,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
       if( effectID == id )
         {
         effectReturned(phase);
-        mCube[phase].remove(id);
+        mObject[phase].remove(id);
         return;
         }
       }
@@ -123,7 +123,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
       if( effectID == id )
         {
         effectReturned(phase);
-        mCube[phase].getEffects().abortById(id);
+        mObject[phase].getEffects().abortById(id);
         return;
         }
       }
@@ -142,7 +142,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
         case 0: mPhaseActive[1] = true;
                 mEffectFinished[1] = createEffectsPhase1(mDuration);
                 assignEffects(1);
-                mScreen.attach(mCube[1]);
+                mScreen.attach(mObject[1]);
                 break;
         case 1: mListener.effectFinished(FAKE_EFFECT_ID);
                 break;
@@ -153,7 +153,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
       switch(phase)
         {
         case 0: mPhaseActive[0] = false;
-                mScreen.detach(mCube[0]);
+                mScreen.detach(mObject[0]);
                 break;
         case 1: mPhaseActive[1] = false;
                 break;
@@ -175,11 +175,11 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
 
     for(int i=0; i<mCubeEffectNumber[phase]; i++)
       {
-      mCube[phase].apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
+      mObject[phase].apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
       mCubeEffects[phase][i].notifyWhenFinished(this);
       }
 
-    DistortedEffects nodeEffects = mCube[phase].getEffects();
+    DistortedEffects nodeEffects = mObject[phase].getEffects();
 
     for(int i=0; i<mNodeEffectNumber[phase]; i++)
       {
@@ -227,12 +227,12 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
   public long start(int duration, RubikRenderer renderer)
     {
     mScreen   = renderer.getScreen();
-    mCube[0]  = renderer.getOldCube();
-    mCube[1]  = renderer.getCube();
+    mObject[0]= renderer.getOldObject();
+    mObject[1]= renderer.getObject();
     mListener = renderer;
     mDuration = duration;
 
-    if( mCube[0]!=null )
+    if( mObject[0]!=null )
       {
       mPhaseActive[0] = true;
       mEffectFinished[0] = createEffectsPhase0(mDuration);
@@ -243,7 +243,7 @@ public abstract class SizeChangeEffect extends BaseEffect implements EffectListe
       mPhaseActive[1] = true;
       mEffectFinished[1] = createEffectsPhase1(mDuration);
       assignEffects(1);
-      mScreen.attach(mCube[1]);
+      mScreen.attach(mObject[1]);
       }
 
     return FAKE_EFFECT_ID;
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffect.java b/src/main/java/org/distorted/effect/solve/SolveEffect.java
index 1d3dd709..4469a481 100644
--- a/src/main/java/org/distorted/effect/solve/SolveEffect.java
+++ b/src/main/java/org/distorted/effect/solve/SolveEffect.java
@@ -24,8 +24,8 @@ 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.object.RubikCube;
 import org.distorted.magic.RubikRenderer;
+import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
 
@@ -69,7 +69,7 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
   private int[] mCubeEffectNumber, mNodeEffectNumber;
   private int mPhase;
 
-  RubikCube mCube;
+  RubikObject mObject;
   DistortedScreen mScreen;
   Effect[][] mCubeEffects;
   int[][] mCubeEffectPosition;
@@ -109,11 +109,11 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
 
     for(int i=0; i<mCubeEffectNumber[phase]; i++)
       {
-      mCube.apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
+      mObject.apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
       mCubeEffects[phase][i].notifyWhenFinished(this);
       }
 
-    DistortedEffects nodeEffects = mCube.getEffects();
+    DistortedEffects nodeEffects = mObject.getEffects();
 
     for(int i=0; i<mNodeEffectNumber[phase]; i++)
       {
@@ -130,7 +130,7 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
       {
       case 0: mEffectReturned = 0;
               mPhase          = 1;
-              mCube.solve();
+              mObject.solve();
               createEffectsPhase1(mDuration);
               assignEffects(mPhase);
               break;
@@ -177,7 +177,7 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
       if( effectID == id )
         {
         if( ++mEffectReturned == total ) effectAction(mPhase);
-        mCube.remove(id);
+        mObject.remove(id);
         return;
         }
       }
@@ -188,7 +188,7 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
       if( effectID == id )
         {
         if( ++mEffectReturned == total ) effectAction(mPhase);
-        mCube.getEffects().abortById(id);
+        mObject.getEffects().abortById(id);
         return;
         }
       }
@@ -200,7 +200,7 @@ public abstract class SolveEffect extends BaseEffect implements EffectListener
   public long start(int duration, RubikRenderer renderer)
     {
     mScreen   = renderer.getScreen();
-    mCube     = renderer.getCube();
+    mObject   = renderer.getObject();
     mListener = renderer;
     mDuration = duration;
 
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java b/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java
index 96fd8928..8dcd24fa 100644
--- a/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java
+++ b/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java
@@ -74,7 +74,7 @@ public class SolveEffectSpin extends SolveEffect
     mCubeEffectPosition[phase] = new int[] {3};
     mCubeEffects[phase]        = new Effect[mCubeEffectPosition[0].length];
 
-    Static4D quaternion = mCube.getRotationQuat();                        // always rotate around
+    Static4D quaternion = mObject.getRotationQuat();                      // always rotate around
     Static4D tmpAxis    = new Static4D(0,1,0,0);                          // vert axis no matter
     Static4D rotated    = rotateVectorByInvertedQuat(tmpAxis,quaternion); // how cube is rotated
 
diff --git a/src/main/java/org/distorted/effect/win/WinEffect.java b/src/main/java/org/distorted/effect/win/WinEffect.java
index f5942e68..58d7e400 100644
--- a/src/main/java/org/distorted/effect/win/WinEffect.java
+++ b/src/main/java/org/distorted/effect/win/WinEffect.java
@@ -24,8 +24,8 @@ 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.object.RubikCube;
 import org.distorted.magic.RubikRenderer;
+import org.distorted.object.RubikObject;
 
 import java.lang.reflect.Method;
 
@@ -67,7 +67,7 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
   private int mEffectReturned;
   private int mCubeEffectNumber, mNodeEffectNumber;
 
-  RubikCube mCube;
+  RubikObject mObject;
   DistortedScreen mScreen;
   Effect[] mCubeEffects;
   int[] mCubeEffectPosition;
@@ -92,11 +92,11 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
 
     for(int i=0; i<mCubeEffectNumber; i++)
       {
-      mCube.apply(mCubeEffects[i],mCubeEffectPosition[i]);
+      mObject.apply(mCubeEffects[i],mCubeEffectPosition[i]);
       mCubeEffects[i].notifyWhenFinished(this);
       }
 
-    DistortedEffects nodeEffects = mCube.getEffects();
+    DistortedEffects nodeEffects = mObject.getEffects();
 
     for(int i=0; i<mNodeEffectNumber; i++)
       {
@@ -143,7 +143,7 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
       if( effectID == id )
         {
         if( ++mEffectReturned == total ) mListener.effectFinished(FAKE_EFFECT_ID);
-        mCube.remove(id);
+        mObject.remove(id);
         return;
         }
       }
@@ -154,7 +154,7 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
       if( effectID == id )
         {
         if( ++mEffectReturned == total ) mListener.effectFinished(FAKE_EFFECT_ID);
-        mCube.getEffects().abortById(id);
+        mObject.getEffects().abortById(id);
         return;
         }
       }
@@ -166,7 +166,7 @@ public abstract class WinEffect extends BaseEffect implements EffectListener
   public long start(int duration, RubikRenderer renderer)
     {
     mScreen   = renderer.getScreen();
-    mCube     = renderer.getCube();
+    mObject   = renderer.getObject();
     mListener = renderer;
     mDuration = duration;
 
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index ecbb69e1..6885b84b 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -32,7 +32,7 @@ import org.distorted.effect.BaseEffect;
 import org.distorted.library.main.DistortedLibrary;
 
 import org.distorted.network.RubikScoresDownloader;
-import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectList;
 import org.distorted.uistate.RubikState;
 import org.distorted.uistate.RubikStateAbstract;
 import org.distorted.uistate.RubikStatePlay;
@@ -72,6 +72,9 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
       view.onResume();
       restorePreferences();
       RubikState.setState(this);
+      RubikStatePlay play = (RubikStatePlay)RubikState.PLAY.getStateClass();
+      int ordinal = play.getButton();
+      view.getRenderer().createObject(RubikObjectList.getObject(ordinal));
       }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -90,12 +93,12 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
       {
       int id = v.getId();
 
-      if( id>=0 && id< RubikObject.LENGTH )
+      if( id>=0 && id< RubikObjectList.LENGTH )
         {
-        int size = RubikObject.getObject(id).getObjectSize();
+        RubikObjectList object = RubikObjectList.getObject(id);
 
         RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-        boolean success = view.getRenderer().createCube(size);
+        boolean success = view.getRenderer().createObject(object);
 
         if( success )
           {
@@ -203,7 +206,7 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       RubikStatePlay play = (RubikStatePlay)RubikState.PLAY.getStateClass();
       int scramble = play.getPicker();
-      view.getRenderer().scrambleCube(scramble);
+      view.getRenderer().scrambleObject(scramble);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -211,6 +214,6 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
     public void Solve(View v)
       {
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.getRenderer().solveCube();
+      view.getRenderer().solveObject();
       }
 }
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index 2cf94582..a69ac598 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -30,10 +30,9 @@ import org.distorted.library.main.DistortedScreen;
 import org.distorted.library.main.DistortedTexture;
 import org.distorted.library.mesh.MeshFlat;
 import org.distorted.library.message.EffectListener;
-import org.distorted.object.RubikCube;
 import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectList;
 import org.distorted.uistate.RubikState;
-import org.distorted.uistate.RubikStatePlay;
 import org.distorted.uistate.RubikStateSolving;
 
 import javax.microedition.khronos.egl.EGLConfig;
@@ -48,14 +47,15 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
     private RubikSurfaceView mView;
     private DistortedScreen mScreen;
-    private int mNextCubeSize, mScrambleCubeNum;
+    private RubikObjectList mNextObject;
+    private int mScrambleObjectNum;
     private long mRotationFinishedID;
     private long[] mEffectID;
     private boolean mFinishRotation, mRemoveRotation, mSetQuatCurrent, mSetQuatAccumulated;
-    private boolean mSizeChangeCube, mSolveCube, mScrambleCube;
+    private boolean mChangeObject, mSolveObject, mScrambleObject;
     private boolean mCanRotate, mCanDrag, mCanUI;
     private boolean mIsSolved;
-    private RubikCube mOldCube, mNewCube;
+    private RubikObject mOldObject, mNewObject;
     private int mScreenWidth, mScreenHeight;
     private MeshFlat mMesh;
     private SharedPreferences mPreferences;
@@ -67,19 +67,19 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       mView = v;
       mScreen = new DistortedScreen();
 
-      mOldCube = null;
-      mNewCube = null;
+      mOldObject = null;
+      mNewObject = null;
 
       mScreenWidth = mScreenHeight = 0;
-      mScrambleCubeNum = 0;
+      mScrambleObjectNum = 0;
 
       mFinishRotation     = false;
       mRemoveRotation     = false;
       mSetQuatCurrent     = false;
       mSetQuatAccumulated = false;
-      mSizeChangeCube     = true;
-      mSolveCube          = false;
-      mScrambleCube       = false;
+      mChangeObject       = false;
+      mSolveObject        = false;
+      mScrambleObject     = false;
 
       mCanRotate   = true;
       mCanDrag     = true;
@@ -88,31 +88,29 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       mEffectID = new long[BaseEffect.Type.LENGTH];
 
       mMesh= new MeshFlat(20,20);
-
-      RubikStatePlay play = (RubikStatePlay) RubikState.PLAY.getStateClass();
-      int size = play.getButton();
-      mNextCubeSize = RubikObject.getObject(size).getObjectSize();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   private void createCubeNow(int newSize)
+   private void createObjectNow(RubikObjectList object)
      {
-     boolean firstTime = (mOldCube==null && mNewCube==null);
+     boolean firstTime = (mNewObject==null);
 
-     if( mOldCube!=null ) mOldCube.releaseResources();
-     mOldCube = mNewCube;
+     if( mOldObject!=null ) mOldObject.releaseResources();
+     mOldObject = mNewObject;
 
      DistortedTexture texture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
      DistortedEffects effects = new DistortedEffects();
 
-     mNewCube = new RubikCube(newSize, mView.getQuatCurrent(), mView.getQuatAccumulated(), texture, mMesh, effects);
-     mNewCube.createTexture();
-     if( firstTime ) mNewCube.restorePreferences(mPreferences);
+     mNewObject = object.create(mView.getQuatCurrent(), mView.getQuatAccumulated(), texture, mMesh, effects);
+     mNewObject.createTexture();
+     mView.setMovement(object.getObjectMovementClass());
+
+     if( firstTime ) mNewObject.restorePreferences(mPreferences);
 
      if( mScreenWidth!=0 )
        {
-       mNewCube.recomputeScaleFactor(mScreenWidth, mScreenHeight);
+       mNewObject.recomputeScaleFactor(mScreenWidth, mScreenHeight);
        }
 
      mIsSolved = true;
@@ -143,7 +141,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
    void savePreferences(SharedPreferences.Editor editor)
      {
-     mNewCube.savePreferences(editor);
+     mNewObject.savePreferences(editor);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -163,12 +161,12 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   boolean createCube(int newSize)
+   boolean createObject(RubikObjectList object)
      {
-     if( mCanDrag && mCanRotate && (mNewCube==null || newSize != mNewCube.getSize()) )
+     if( mCanDrag && mCanRotate && object!=mNextObject )
        {
-       mSizeChangeCube = true;
-       mNextCubeSize = newSize;
+       mChangeObject = true;
+       mNextObject = object;
        return true;
        }
 
@@ -177,22 +175,22 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   void scrambleCube(int num)
+   void scrambleObject(int num)
      {
      if( mCanUI )
        {
-       mScrambleCube = true;
-       mScrambleCubeNum = num;
+       mScrambleObject = true;
+       mScrambleObjectNum = num;
        }
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   void solveCube()
+   void solveObject()
      {
      if( mCanUI )
        {
-       mSolveCube = true;
+       mSolveObject = true;
        }
      }
 
@@ -250,15 +248,15 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
        mFinishRotation = false;
        mCanRotate      = false;
        mCanUI          = false;
-       mRotationFinishedID = mNewCube.finishRotationNow(this);
+       mRotationFinishedID = mNewObject.finishRotationNow(this);
        }
 
      if( mRemoveRotation )
        {
        mRemoveRotation=false;
-       mNewCube.removeRotationNow();
+       mNewObject.removeRotationNow();
 
-       boolean solved = mNewCube.isSolved();
+       boolean solved = mNewObject.isSolved();
 
        if( solved && !mIsSolved )
          {
@@ -283,31 +281,31 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
        mIsSolved = solved;
        }
 
-     if( mSizeChangeCube )
+     if( mChangeObject )
        {
-       mSizeChangeCube = false;
-       mCanDrag        = false;
-       mCanRotate      = false;
-       mCanUI          = false;
-       createCubeNow(mNextCubeSize);
+       mChangeObject = false;
+       mCanDrag      = false;
+       mCanRotate    = false;
+       mCanUI        = false;
+       createObjectNow(mNextObject);
        doEffectNow( BaseEffect.Type.SIZECHANGE );
        }
 
-     if( mSolveCube )
+     if( mSolveObject )
        {
-       mSolveCube      = false;
+       mSolveObject    = false;
        mCanDrag        = false;
        mCanRotate      = false;
        mCanUI          = false;
        doEffectNow( BaseEffect.Type.SOLVE );
        }
 
-     if( mScrambleCube )
+     if( mScrambleObject )
        {
-       mScrambleCube = false;
-       mCanDrag      = false;
-       mCanRotate    = false;
-       mCanUI        = false;
+       mScrambleObject = false;
+       mCanDrag        = false;
+       mCanRotate      = false;
+       mCanUI          = false;
        doEffectNow( BaseEffect.Type.SCRAMBLE );
        }
      }
@@ -317,7 +315,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height)
       {
-      if( mNewCube!=null ) mNewCube.createTexture();
+      if( mNewObject!=null ) mNewObject.createTexture();
 
       double halfFOVInRadians = Math.atan( 1.0f/(2*CAMERA_DISTANCE) );
       float fovInDegrees = (float)(2*halfFOVInRadians*(180/Math.PI));
@@ -326,9 +324,9 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       mScreen.resize(width, height);
       mView.setScreenSize(width,height);
 
-      if( mNewCube!=null )
+      if( mNewObject!=null )
         {
-        mNewCube.recomputeScaleFactor(width,height);
+        mNewObject.recomputeScaleFactor(width,height);
         }
 
       mScreenHeight = height;
@@ -394,16 +392,16 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public RubikCube getCube()
+   public RubikObject getObject()
      {
-     return mNewCube;
+     return mNewObject;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public RubikCube getOldCube()
+   public RubikObject getOldObject()
      {
-     return mOldCube;
+     return mOldObject;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -417,6 +415,6 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
    public int getNumScrambles()
      {
-     return mScrambleCubeNum;
+     return mScrambleObjectNum;
      }
 }
diff --git a/src/main/java/org/distorted/magic/RubikSurfaceView.java b/src/main/java/org/distorted/magic/RubikSurfaceView.java
index 19aa3e05..c417ef8e 100644
--- a/src/main/java/org/distorted/magic/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/magic/RubikSurfaceView.java
@@ -28,8 +28,8 @@ import android.view.MotionEvent;
 
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
-import org.distorted.object.RubikCube;
-import org.distorted.object.RubikCubeMovement;
+import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectMovement;
 import org.distorted.uistate.RubikState;
 import org.distorted.uistate.RubikStateSolving;
 
@@ -48,7 +48,7 @@ public class RubikSurfaceView extends GLSurfaceView
     private final Static4D CAMERA_POINT = new Static4D(0, 0, RubikRenderer.CAMERA_DISTANCE, 0);
 
     private RubikRenderer mRenderer;
-    private RubikCubeMovement mMovement;
+    private RubikObjectMovement mMovement;
     private boolean mDragging, mBeginningRotation, mContinuingRotation;
     private float mX, mY;
     private int mScreenWidth, mScreenHeight, mScreenMin;
@@ -103,6 +103,13 @@ public class RubikSurfaceView extends GLSurfaceView
       return mQuatCurrent;
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setMovement(RubikObjectMovement movement)
+      {
+      mMovement = movement;
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     private Static4D quatFromDrag(float dragX, float dragY)
@@ -196,7 +203,6 @@ public class RubikSurfaceView extends GLSurfaceView
       if(!isInEditMode())
         {
         mRenderer = new RubikRenderer(this);
-        mMovement = new RubikCubeMovement();
 
         final ActivityManager activityManager     = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
@@ -259,9 +265,9 @@ public class RubikSurfaceView extends GLSurfaceView
                                            Static4D rotatedTouchPoint2= rotateVectorByInvertedQuat(touchPoint2, mQuatAccumulated);
 
                                            Static2D rot = mMovement.newRotation(rotatedTouchPoint2);
-                                           RubikCube cube = mRenderer.getCube();
+                                           RubikObject object = mRenderer.getObject();
 
-                                           cube.addNewRotation( (int)rot.get0(), (int)(cube.getSize()*rot.get1()) );
+                                           object.beginNewRotation( (int)rot.get0(), (int)(object.getSize()*rot.get1()) );
 
                                            if( RubikState.getCurrentState()==RubikState.SOLV )
                                              {
@@ -279,7 +285,7 @@ public class RubikSurfaceView extends GLSurfaceView
                                          Static4D rotatedTouchPoint3= rotateVectorByInvertedQuat(touchPoint3, mQuatAccumulated);
 
                                          float angle = mMovement.continueRotation(rotatedTouchPoint3);
-                                         mRenderer.getCube().continueRotation(SWIPING_SENSITIVITY*angle);
+                                         mRenderer.getObject().continueRotation(SWIPING_SENSITIVITY*angle);
                                          }
                                        break;
          case MotionEvent.ACTION_UP  : if( mDragging )
diff --git a/src/main/java/org/distorted/network/RubikScoresDownloader.java b/src/main/java/org/distorted/network/RubikScoresDownloader.java
index ddfcba61..c2111f65 100644
--- a/src/main/java/org/distorted/network/RubikScoresDownloader.java
+++ b/src/main/java/org/distorted/network/RubikScoresDownloader.java
@@ -24,7 +24,7 @@ import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.UnknownHostException;
 
-import static org.distorted.object.RubikObject.LENGTH;
+import static org.distorted.object.RubikObjectList.LENGTH;
 import static org.distorted.uistate.RubikStatePlay.MAX_SCRAMBLE;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/object/RubikCube.java b/src/main/java/org/distorted/object/RubikCube.java
index 3724da86..43d6d378 100644
--- a/src/main/java/org/distorted/object/RubikCube.java
+++ b/src/main/java/org/distorted/object/RubikCube.java
@@ -28,8 +28,6 @@ import org.distorted.library.effect.Effect;
 import org.distorted.library.effect.MatrixEffectMove;
 import org.distorted.library.effect.MatrixEffectQuaternion;
 import org.distorted.library.effect.MatrixEffectRotate;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedNode;
 import org.distorted.library.main.DistortedTexture;
@@ -42,22 +40,18 @@ import org.distorted.library.type.Static3D;
 import org.distorted.library.type.Static4D;
 import org.distorted.magic.RubikSurfaceView;
 
+import static org.distorted.object.RubikObjectList.VECTX;
+import static org.distorted.object.RubikObjectList.VECTY;
+import static org.distorted.object.RubikObjectList.VECTZ;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikCube extends DistortedNode
+class RubikCube extends RubikObject
 {
-            static final float CUBE_SCREEN_RATIO = 0.5f;
-    private static final int POST_ROTATION_MILLISEC = 500;
-    private static final int TEXTURE_SIZE = 100;
-
     private static final Static3D VectX = new Static3D(1,0,0);
     private static final Static3D VectY = new Static3D(0,1,0);
     private static final Static3D VectZ = new Static3D(0,0,1);
 
-    public static final int VECTX = 0;  //
-    public static final int VECTY = 1;  // don't change this
-    public static final int VECTZ = 2;  //
-
     private DistortedNode[][][] mNodes;
     private MeshCubes[][][] mCubes;
     private DistortedEffects[][][] mEffects;
@@ -66,27 +60,6 @@ public class RubikCube extends DistortedNode
     private Dynamic1D[][][] mRotationAngle;
     private Static3D[][][] mCurrentPosition;
     private MatrixEffectRotate[][][] mRotateEffect;
-    private Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
-    private Static3D mMove, mScale, mNodeMove, mNodeScale;
-    private Static4D mQuatAccumulated;
-    private DistortedTexture mTexture;
-
-    private int mRotAxis, mRotRow;
-    private int mSize;
-
-    private DistortedTexture mNodeTexture;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private int computeNearestAngle(float angle)
-      {
-      final int NEAREST = 90;
-
-      int tmp = (int)((angle+NEAREST/2)/NEAREST);
-      if( angle< -(NEAREST*0.5) ) tmp-=1;
-
-      return NEAREST*tmp;
-      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // All legal rotation quats must have all four of their components equal to either
@@ -158,20 +131,6 @@ public class RubikCube extends DistortedNode
       mQuatScramble[i][j][k].set(x,y,z,w);
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private float getSinkStrength()
-      {
-      switch(mSize)
-        {
-        case 1 : return 1.1f;
-        case 2 : return 1.5f;
-        case 3 : return 1.8f;
-        case 4 : return 2.0f;
-        default: return 3.0f - 4.0f/mSize;
-        }
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     private boolean belongsToRotation(int x, int y, int z, int vector, int row)
@@ -212,28 +171,11 @@ public class RubikCube extends DistortedNode
       mCurrentPosition[x][y][z].set2(roundedZ);
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public RubikCube(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshFlat mesh, DistortedEffects effects)
+    RubikCube(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshFlat mesh, DistortedEffects effects)
       {
-      super(texture,effects,mesh);
-
-      mNodeTexture = texture;
-
-      mSize = size;
-
-      mRotationAngleStatic = new Static1D(0);
-      mRotationAngleMiddle = new Static1D(0);
-      mRotationAngleFinal  = new Static1D(0);
-
-      mMove     = new Static3D(0,0,0);
-      mScale    = new Static3D(1,1,1);
-      mNodeMove = new Static3D(0,0,0);
-      mNodeScale= new Static3D(1,1,1);
-
-      mQuatAccumulated = quatAcc;
+      super(size,quatCur,quatAcc,texture,mesh,effects);
 
       mRotAxis = VECTX;
       mTexture = new DistortedTexture(TEXTURE_SIZE,TEXTURE_SIZE);
@@ -248,22 +190,7 @@ public class RubikCube extends DistortedNode
       mRotateEffect   = new MatrixEffectRotate[mSize][mSize][mSize];
 
       Static3D[][][] cubeVectors = new Static3D[mSize][mSize][mSize];
-
-      Static3D sinkCenter = new Static3D(TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f);
       Static3D matrCenter = new Static3D(0,0,0);
-      Static4D region = new Static4D(0,0,0, TEXTURE_SIZE*0.72f);
-
-      VertexEffectSink        sinkEffect = new VertexEffectSink( new Static1D(getSinkStrength()), sinkCenter, region );
-      MatrixEffectMove        moveEffect = new MatrixEffectMove(mMove);
-      MatrixEffectScale      scaleEffect = new MatrixEffectScale(mScale);
-      MatrixEffectQuaternion quatCEffect = new MatrixEffectQuaternion(quatCur, matrCenter);
-      MatrixEffectQuaternion quatAEffect = new MatrixEffectQuaternion(quatAcc, matrCenter);
-
-      MatrixEffectMove       nodeMoveEffect  = new MatrixEffectMove(mNodeMove);
-      MatrixEffectScale      nodeScaleEffect = new MatrixEffectScale(mNodeScale);
-
-      effects.apply(nodeScaleEffect);
-      effects.apply(nodeMoveEffect);
 
       // 3x2 bitmap = 6 squares:
       //
@@ -311,14 +238,14 @@ public class RubikCube extends DistortedNode
               mRotateEffect[x][y][z]    = new MatrixEffectRotate(mRotationAngle[x][y][z], mRotationAxis[x][y][z], matrCenter);
 
               mEffects[x][y][z] = new DistortedEffects();
-              mEffects[x][y][z].apply(sinkEffect);
+              mEffects[x][y][z].apply(mSinkEffect);
               mEffects[x][y][z].apply( new MatrixEffectMove(cubeVectors[x][y][z]) );
               mEffects[x][y][z].apply( new MatrixEffectQuaternion(mQuatScramble[x][y][z], matrCenter));
               mEffects[x][y][z].apply(mRotateEffect[x][y][z]);
-              mEffects[x][y][z].apply(quatAEffect);
-              mEffects[x][y][z].apply(quatCEffect);
-              mEffects[x][y][z].apply(scaleEffect);
-              mEffects[x][y][z].apply(moveEffect);
+              mEffects[x][y][z].apply(mQuatAEffect);
+              mEffects[x][y][z].apply(mQuatCEffect);
+              mEffects[x][y][z].apply(mScaleEffect);
+              mEffects[x][y][z].apply(mMoveEffect);
 
               mNodes[x][y][z] = new DistortedNode(mTexture,mEffects[x][y][z],mCubes[x][y][z]);
 
@@ -328,50 +255,7 @@ public class RubikCube extends DistortedNode
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void addNewRotation(int vector, int row )
-      {
-      Static3D axis = VectX;
-
-      switch(vector)
-        {
-        case VECTX: axis = VectX; break;
-        case VECTY: axis = VectY; break;
-        case VECTZ: axis = VectZ; break;
-        }
-
-      mRotAxis = vector;
-      mRotRow  = row;
-
-      mRotationAngleStatic.set0(0.0f);
-
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              if( belongsToRotation(x,y,z,vector,mRotRow) )
-                {
-                mRotationAxis[x][y][z].set(axis);
-                mRotationAngle[x][y][z].add(mRotationAngleStatic);
-                }
-              }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void continueRotation(float angleInDegrees)
-      {
-      mRotationAngleStatic.set0(angleInDegrees);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public Static4D getRotationQuat()
-      {
-      return mQuatAccumulated;
-      }
-
+// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // mSize already saved as RubikStatePlay.mButton
 
@@ -419,228 +303,224 @@ public class RubikCube extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public long finishRotationNow(EffectListener listener)
-      {
-      boolean first = true;
-      long effectID=0;
-
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              if( belongsToRotation(x,y,z,mRotAxis,mRotRow) )
-                {
-                if( first )
-                  {
-                  first = false;
-                  mRotateEffect[x][y][z].notifyWhenFinished(listener);
-                  effectID = mRotateEffect[x][y][z].getID();
-                  int pointNum = mRotationAngle[x][y][z].getNumPoints();
-
-                  if( pointNum>=1 )
-                    {
-                    float startingAngle = mRotationAngle[x][y][z].getPoint(pointNum-1).get0();
-                    int nearestAngleInDegrees = computeNearestAngle(startingAngle);
-                    mRotationAngleStatic.set0(startingAngle);
-                    mRotationAngleFinal.set0(nearestAngleInDegrees);
-                    mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-startingAngle)*0.2f );
-                    }
-                  else
-                    {
-                    android.util.Log.e("cube", "ERROR finishing rotation!");
-                    return 0;
-                    }
-                  }
-
-                mRotationAngle[x][y][z].setDuration(POST_ROTATION_MILLISEC);
-                mRotationAngle[x][y][z].resetToBeginning();
-                mRotationAngle[x][y][z].removeAll();
-                mRotationAngle[x][y][z].add(mRotationAngleStatic);
-                mRotationAngle[x][y][z].add(mRotationAngleMiddle);
-                mRotationAngle[x][y][z].add(mRotationAngleFinal);
-                }
-              }
+   public long finishRotationNow(EffectListener listener)
+     {
+     boolean first = true;
+     long effectID=0;
 
-      return effectID;
-      }
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             if( belongsToRotation(x,y,z,mRotAxis,mRotRow) )
+               {
+               if( first )
+                 {
+                 first = false;
+                 mRotateEffect[x][y][z].notifyWhenFinished(listener);
+                 effectID = mRotateEffect[x][y][z].getID();
+                 int pointNum = mRotationAngle[x][y][z].getNumPoints();
+
+                 if( pointNum>=1 )
+                   {
+                   float startingAngle = mRotationAngle[x][y][z].getPoint(pointNum-1).get0();
+                   int nearestAngleInDegrees = computeNearestAngle(startingAngle);
+                   mRotationAngleStatic.set0(startingAngle);
+                   mRotationAngleFinal.set0(nearestAngleInDegrees);
+                   mRotationAngleMiddle.set0( nearestAngleInDegrees + (nearestAngleInDegrees-startingAngle)*0.2f );
+                   }
+                 else
+                   {
+                   android.util.Log.e("cube", "ERROR finishing rotation!");
+                   return 0;
+                   }
+                 }
+
+               mRotationAngle[x][y][z].setDuration(POST_ROTATION_MILLISEC);
+               mRotationAngle[x][y][z].resetToBeginning();
+               mRotationAngle[x][y][z].removeAll();
+               mRotationAngle[x][y][z].add(mRotationAngleStatic);
+               mRotationAngle[x][y][z].add(mRotationAngleMiddle);
+               mRotationAngle[x][y][z].add(mRotationAngleFinal);
+               }
+             }
+
+     return effectID;
+     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // all DistortedTextures, DistortedNodes, DistortedFramebuffers, DistortedScreens and all types of
 // Meshes HAVE TO be markedForDeletion when they are no longer needed- otherwise we have a major
 // memory leak.
 
-    public void releaseResources()
-      {
-      mTexture.markForDeletion();
+   public void releaseResources()
+     {
+     mTexture.markForDeletion();
 
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            {
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              mCubes[x][y][z].markForDeletion();
-              mNodes[x][y][z].markForDeletion();
-              }
-            }
-      }
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           {
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             mCubes[x][y][z].markForDeletion();
+             mNodes[x][y][z].markForDeletion();
+             }
+           }
+     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void createTexture()
-      {
-      Bitmap bitmap;
-
-      final int S = 128;
-      final int W = 3*S;
-      final int H = 2*S;
-      final int R = S/10;
-      final int M = S/20;
-
-      Paint paint = new Paint();
-      bitmap = Bitmap.createBitmap(W,H, Bitmap.Config.ARGB_8888);
-      Canvas canvas = new Canvas(bitmap);
-
-      paint.setAntiAlias(true);
-      paint.setTextAlign(Paint.Align.CENTER);
-      paint.setStyle(Paint.Style.FILL);
-
-      // 3x2 bitmap = 6 squares:
-      //
-      // RED     GREEN   BLUE
-      // YELLOW  WHITE   BROWN
-
-      paint.setColor(0xff000000);                                  // BLACK BACKGROUND
-      canvas.drawRect(0, 0, W, H, paint);                          //
-
-      paint.setColor(0xffff0000);                                  // RED
-      canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
-      paint.setColor(0xff00ff00);                                  // GREEN
-      canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
-      paint.setColor(0xff0000ff);                                  // BLUE
-      canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
-      paint.setColor(0xffffff00);                                  // YELLOW
-      canvas.drawRoundRect(    M, S+M,   S-M, 2*S-M, R, R, paint); //
-      paint.setColor(0xffffffff);                                  // WHITE
-      canvas.drawRoundRect(  S+M, S+M, 2*S-M, 2*S-M, R, R, paint); //
-      paint.setColor(0xffb5651d);                                  // BROWN
-      canvas.drawRoundRect(2*S+M, S+M, 3*S-M, 2*S-M, R, R, paint); //
-
-      mTexture.setTexture(bitmap);
-      }
+   public void createTexture()
+     {
+     Bitmap bitmap;
+
+     final int S = 128;
+     final int W = 3*S;
+     final int H = 2*S;
+     final int R = S/10;
+     final int M = S/20;
+
+     Paint paint = new Paint();
+     bitmap = Bitmap.createBitmap(W,H, Bitmap.Config.ARGB_8888);
+     Canvas canvas = new Canvas(bitmap);
+
+     paint.setAntiAlias(true);
+     paint.setTextAlign(Paint.Align.CENTER);
+     paint.setStyle(Paint.Style.FILL);
+
+     // 3x2 bitmap = 6 squares:
+     //
+     // RED     GREEN   BLUE
+     // YELLOW  WHITE   BROWN
+
+     paint.setColor(0xff000000);                                  // BLACK BACKGROUND
+     canvas.drawRect(0, 0, W, H, paint);                          //
+
+     paint.setColor(0xffff0000);                                  // RED
+     canvas.drawRoundRect(    M,   M,   S-M,   S-M, R, R, paint); //
+     paint.setColor(0xff00ff00);                                  // GREEN
+     canvas.drawRoundRect(  S+M,   M, 2*S-M,   S-M, R, R, paint); //
+     paint.setColor(0xff0000ff);                                  // BLUE
+     canvas.drawRoundRect(2*S+M,   M, 3*S-M,   S-M, R, R, paint); //
+     paint.setColor(0xffffff00);                                  // YELLOW
+     canvas.drawRoundRect(    M, S+M,   S-M, 2*S-M, R, R, paint); //
+     paint.setColor(0xffffffff);                                  // WHITE
+     canvas.drawRoundRect(  S+M, S+M, 2*S-M, 2*S-M, R, R, paint); //
+     paint.setColor(0xffb5651d);                                  // BROWN
+     canvas.drawRoundRect(2*S+M, S+M, 3*S-M, 2*S-M, R, R, paint); //
+
+     mTexture.setTexture(bitmap);
+     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void recomputeScaleFactor(int screenWidth, int screenHeight)
-      {
-      int texW = mNodeTexture.getWidth();
-      int texH = mNodeTexture.getHeight();
-
-      if( (float)texH/texW > (float)screenHeight/screenWidth )
-        {
-        int w = (screenHeight*texW)/texH;
-        float factor = (float)screenHeight/texH;
-        mNodeMove.set((screenWidth-w)*0.5f ,0, 0);
-        mNodeScale.set(factor,factor,factor);
-        }
-      else
-        {
-        int h = (screenWidth*texH)/texW;
-        float factor = (float)screenWidth/texW;
-        mNodeMove.set(0,(screenHeight-h)*0.5f,0);
-        mNodeScale.set(factor,factor,factor);
-        }
-
-      float scaleFactor = (CUBE_SCREEN_RATIO*texW/(TEXTURE_SIZE*mSize));
-
-      mMove.set( texW*0.5f , texH*0.5f , 0.0f );
-      mScale.set(scaleFactor,scaleFactor,scaleFactor);
+   public void apply(Effect effect, int position)
+     {
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           {
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             mEffects[x][y][z].apply(effect, position);
+             }
+           }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void apply(Effect effect, int position)
-      {
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            {
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              mEffects[x][y][z].apply(effect, position);
-              }
-            }
+   public void remove(long effectID)
+     {
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           {
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             mEffects[x][y][z].abortById(effectID);
+             }
+           }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void remove(long effectID)
-      {
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            {
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              mEffects[x][y][z].abortById(effectID);
-              }
-            }
+   public void solve()
+     {
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             mQuatScramble[x][y][z].set(0,0,0,1);
+             mCurrentPosition[x][y][z].set(x,y,z);
+             }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void solve()
-      {
-      for(int x=0; x<mSize; x++)
-        for(int y=0; y<mSize; y++)
-          for(int z=0; z<mSize; z++)
-            if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
-              {
-              mQuatScramble[x][y][z].set(0,0,0,1);
-              mCurrentPosition[x][y][z].set(x,y,z);
-              }
-      }
+   public boolean isSolved()
+     {
+     Static4D q = mQuatScramble[0][0][0];
+
+     float x = q.get0();
+     float y = q.get1();
+     float z = q.get2();
+     float w = q.get3();
+
+     for(int i = 0; i< mSize; i++)
+       for(int j = 0; j< mSize; j++)
+         for(int k = 0; k< mSize; k++)
+           {
+           if( i==0 || i==mSize-1 || j==0 || j==mSize-1 || k==0 || k==mSize-1 )
+             {
+             q = mQuatScramble[i][j][k];
+
+             if( q.get0()!=x || q.get1()!=y || q.get2()!=z || q.get3()!=w )
+               {
+               return false;
+               }
+             }
+           }
+
+     return true;
+     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public boolean isSolved()
-      {
-      Static4D q = mQuatScramble[0][0][0];
-
-      float x = q.get0();
-      float y = q.get1();
-      float z = q.get2();
-      float w = q.get3();
-
-      for(int i = 0; i< mSize; i++)
-        for(int j = 0; j< mSize; j++)
-          for(int k = 0; k< mSize; k++)
-            {
-            if( i==0 || i==mSize-1 || j==0 || j==mSize-1 || k==0 || k==mSize-1 )
-              {
-              q = mQuatScramble[i][j][k];
+   public void beginNewRotation(int vector, int row )
+     {
+     Static3D axis = VectX;
 
-              if( q.get0()!=x || q.get1()!=y || q.get2()!=z || q.get3()!=w )
-                {
-                return false;
-                }
-              }
-            }
+     switch(vector)
+       {
+       case VECTX: axis = VectX; break;
+       case VECTY: axis = VectY; break;
+       case VECTZ: axis = VectZ; break;
+       }
 
-      return true;
-      }
+     mRotAxis = vector;
+     mRotRow  = row;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+     mRotationAngleStatic.set0(0.0f);
 
-    public int getSize()
-      {
-      return mSize;
-      }
+     for(int x=0; x<mSize; x++)
+       for(int y=0; y<mSize; y++)
+         for(int z=0; z<mSize; z++)
+           if( x==0 || x==mSize-1 || y==0 || y==mSize-1 || z==0 || z==mSize-1 )
+             {
+             if( belongsToRotation(x,y,z,vector,mRotRow) )
+               {
+               mRotationAxis[x][y][z].set(axis);
+               mRotationAngle[x][y][z].add(mRotationAngleStatic);
+               }
+             }
+     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener )
+   public long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener )
       {
       Static3D axis = VectX;
       long effectID=0;
@@ -685,7 +565,7 @@ public class RubikCube extends DistortedNode
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void removeRotationNow()
+   public void removeRotationNow()
       {
       float qx=0,qy=0,qz=0;
       boolean first = true;
diff --git a/src/main/java/org/distorted/object/RubikCubeMovement.java b/src/main/java/org/distorted/object/RubikCubeMovement.java
index 26a45f6c..019e3f28 100644
--- a/src/main/java/org/distorted/object/RubikCubeMovement.java
+++ b/src/main/java/org/distorted/object/RubikCubeMovement.java
@@ -22,9 +22,13 @@ package org.distorted.object;
 import org.distorted.library.type.Static2D;
 import org.distorted.library.type.Static4D;
 
+import static org.distorted.object.RubikObjectList.VECTX;
+import static org.distorted.object.RubikObjectList.VECTY;
+import static org.distorted.object.RubikObjectList.VECTZ;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikCubeMovement
+class RubikCubeMovement extends RubikObjectMovement
 {
     private final static int NONE   =-1;
     private final static int FRONT  = 0;  // has to be 6 consecutive ints
@@ -34,7 +38,7 @@ public class RubikCubeMovement
     private final static int TOP    = 4;  //
     private final static int BOTTOM = 5;  //
 
-    private static final int[] VECT = {RubikCube.VECTX,RubikCube.VECTY,RubikCube.VECTZ};
+    private static final int[] VECT = {VECTX,VECTY,VECTZ};
 
     private float[] mPoint, mCamera, mDiff, mTouch;
     private int mRotationVect, mLastTouchedFace;
@@ -70,11 +74,11 @@ public class RubikCubeMovement
       switch(face)
         {
         case FRONT :
-        case BACK  : return RubikCube.VECTX;
+        case BACK  : return VECTX;
         case LEFT  :
-        case RIGHT : return RubikCube.VECTZ;
+        case RIGHT : return VECTZ;
         case TOP   :
-        case BOTTOM: return RubikCube.VECTX;
+        case BOTTOM: return VECTX;
         }
 
       return -1;
@@ -87,11 +91,11 @@ public class RubikCubeMovement
       switch(face)
         {
         case FRONT :
-        case BACK  : return RubikCube.VECTY;
+        case BACK  : return VECTY;
         case LEFT  :
-        case RIGHT : return RubikCube.VECTY;
+        case RIGHT : return VECTY;
         case TOP   :
-        case BOTTOM: return RubikCube.VECTZ;
+        case BOTTOM: return VECTZ;
         }
 
       return -1;
@@ -104,11 +108,11 @@ public class RubikCubeMovement
       switch(face)
         {
         case FRONT :
-        case BACK  : return RubikCube.VECTZ;
+        case BACK  : return VECTZ;
         case LEFT  :
-        case RIGHT : return RubikCube.VECTX;
+        case RIGHT : return VECTX;
         case TOP   :
-        case BOTTOM: return RubikCube.VECTY;
+        case BOTTOM: return VECTY;
         }
 
       return -1;
@@ -142,11 +146,9 @@ public class RubikCubeMovement
       output[2] = (mPoint[2]-mCamera[2])*ratio + mCamera[2];
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public RubikCubeMovement()
+    RubikCubeMovement()
       {
       mPoint = new float[3];
       mCamera= new float[3];
@@ -154,11 +156,13 @@ public class RubikCubeMovement
       mTouch = new float[3];
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public boolean faceTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
       {
-      float cubeHalfSize= RubikCube.CUBE_SCREEN_RATIO*0.5f;
+      float cubeHalfSize= RubikObject.OBJECT_SCREEN_RATIO*0.5f;
 
       mPoint[0]  = rotatedTouchPoint.get0();
       mPoint[1]  = rotatedTouchPoint.get1();
@@ -190,7 +194,7 @@ public class RubikCubeMovement
 
     public Static2D newRotation(Static4D rotatedTouchPoint)
       {
-      float cubeHalfSize= RubikCube.CUBE_SCREEN_RATIO*0.5f;
+      float cubeHalfSize= RubikObject.OBJECT_SCREEN_RATIO*0.5f;
 
       mPoint[0] = rotatedTouchPoint.get0();
       mPoint[1] = rotatedTouchPoint.get1();
diff --git a/src/main/java/org/distorted/object/RubikObject.java b/src/main/java/org/distorted/object/RubikObject.java
index 8123f027..106b0057 100644
--- a/src/main/java/org/distorted/object/RubikObject.java
+++ b/src/main/java/org/distorted/object/RubikObject.java
@@ -19,60 +19,172 @@
 
 package org.distorted.object;
 
-import org.distorted.magic.R;
+import android.content.SharedPreferences;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.effect.VertexEffectSink;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedNode;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshFlat;
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public enum RubikObject
+public abstract class RubikObject extends DistortedNode
   {
-  CUBE2 ( 2, R.drawable.button2 ),
-  CUBE3 ( 3, R.drawable.button3 ),
-  CUBE4 ( 4, R.drawable.button4 ),
-  CUBE5 ( 5, R.drawable.button5 ),
-  ;
+  static final float OBJECT_SCREEN_RATIO = 0.5f;
+  static final int POST_ROTATION_MILLISEC = 500;
+  static final int TEXTURE_SIZE = 100;
+
+  private Static3D mMove, mScale, mNodeMove, mNodeScale;
+  private Static4D mQuatAccumulated;
+  private DistortedTexture mNodeTexture;
+
+  int mSize, mRotAxis, mRotRow;
 
-  public static final int LENGTH = values().length;
-  private final int mObjectSize, mIconID;
-  private static final RubikObject[] objects;
+  Static1D mRotationAngleStatic, mRotationAngleMiddle, mRotationAngleFinal;
+  DistortedTexture mTexture;
+
+  VertexEffectSink mSinkEffect;
+  MatrixEffectMove mMoveEffect;
+  MatrixEffectScale mScaleEffect;
+  MatrixEffectQuaternion mQuatCEffect;
+  MatrixEffectQuaternion mQuatAEffect;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static
+  RubikObject(int size, Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshFlat mesh, DistortedEffects effects)
     {
-    int i = 0;
-    objects = new RubikObject[LENGTH];
+    super(texture,effects,mesh);
 
-    for(RubikObject size: RubikObject.values())
-      {
-      objects[i] = size;
-      i++;
-      }
+    mNodeTexture = texture;
+
+    mSize = size;
+
+    mRotationAngleStatic = new Static1D(0);
+    mRotationAngleMiddle = new Static1D(0);
+    mRotationAngleFinal  = new Static1D(0);
+
+    mMove     = new Static3D(0,0,0);
+    mScale    = new Static3D(1,1,1);
+    mNodeMove = new Static3D(0,0,0);
+    mNodeScale= new Static3D(1,1,1);
+
+    mQuatAccumulated = quatAcc;
+
+    Static3D sinkCenter = new Static3D(TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f, TEXTURE_SIZE*0.5f);
+    Static3D matrCenter = new Static3D(0,0,0);
+    Static4D region = new Static4D(0,0,0, TEXTURE_SIZE*0.72f);
+
+    mSinkEffect = new VertexEffectSink( new Static1D(getSinkStrength()), sinkCenter, region );
+    mMoveEffect = new MatrixEffectMove(mMove);
+    mScaleEffect = new MatrixEffectScale(mScale);
+    mQuatCEffect = new MatrixEffectQuaternion(quatCur, matrCenter);
+    mQuatAEffect = new MatrixEffectQuaternion(quatAcc, matrCenter);
+
+    MatrixEffectMove  nodeMoveEffect  = new MatrixEffectMove(mNodeMove);
+    MatrixEffectScale nodeScaleEffect = new MatrixEffectScale(mNodeScale);
+
+    effects.apply(nodeScaleEffect);
+    effects.apply(nodeMoveEffect);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int computeNearestAngle(float angle)
+    {
+    final int NEAREST = 90;
+
+    int tmp = (int)((angle+NEAREST/2)/NEAREST);
+    if( angle< -(NEAREST*0.5) ) tmp-=1;
+
+    return NEAREST*tmp;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public static RubikObject getObject(int ordinal)
+  private float getSinkStrength()
     {
-    return objects[ordinal];
+    switch(mSize)
+      {
+      case 1 : return 1.1f;
+      case 2 : return 1.5f;
+      case 3 : return 1.8f;
+      case 4 : return 2.0f;
+      default: return 3.0f - 4.0f/mSize;
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  RubikObject(int size, int iconID)
+  public int getSize()
     {
-    mObjectSize = size;
-    mIconID     = iconID;
+    return mSize;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getIconID()
+  public void continueRotation(float angleInDegrees)
     {
-    return mIconID;
+    mRotationAngleStatic.set0(angleInDegrees);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getObjectSize()
+  public Static4D getRotationQuat()
+      {
+      return mQuatAccumulated;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void recomputeScaleFactor(int screenWidth, int screenHeight)
     {
-    return mObjectSize;
+    int texW = mNodeTexture.getWidth();
+    int texH = mNodeTexture.getHeight();
+
+    if( (float)texH/texW > (float)screenHeight/screenWidth )
+      {
+      int w = (screenHeight*texW)/texH;
+      float factor = (float)screenHeight/texH;
+      mNodeMove.set((screenWidth-w)*0.5f ,0, 0);
+      mNodeScale.set(factor,factor,factor);
+      }
+    else
+      {
+      int h = (screenWidth*texH)/texW;
+      float factor = (float)screenWidth/texW;
+      mNodeMove.set(0,(screenHeight-h)*0.5f,0);
+      mNodeScale.set(factor,factor,factor);
+      }
+
+    float scaleFactor = (OBJECT_SCREEN_RATIO*texW/(TEXTURE_SIZE*mSize));
+
+    mMove.set( texW*0.5f , texH*0.5f , 0.0f );
+    mScale.set(scaleFactor,scaleFactor,scaleFactor);
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public abstract void savePreferences(SharedPreferences.Editor editor);
+  public abstract void restorePreferences(SharedPreferences preferences);
+
+  public abstract void beginNewRotation(int vector, int row );
+  public abstract long addNewRotation(int vector, int row, int angle, long durationMillis, EffectListener listener );
+  public abstract long finishRotationNow(EffectListener listener);
+  public abstract void removeRotationNow();
+
+  public abstract void releaseResources();
+  public abstract void createTexture();
+  public abstract void apply(Effect effect, int position);
+  public abstract void remove(long effectID);
+  public abstract void solve();
+  public abstract boolean isSolved();
   }
diff --git a/src/main/java/org/distorted/object/RubikObjectList.java b/src/main/java/org/distorted/object/RubikObjectList.java
new file mode 100644
index 00000000..fec751d6
--- /dev/null
+++ b/src/main/java/org/distorted/object/RubikObjectList.java
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.object;
+
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedTexture;
+import org.distorted.library.mesh.MeshFlat;
+import org.distorted.library.type.Static4D;
+import org.distorted.magic.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public enum RubikObjectList
+  {
+  CUBE2 ( 2, R.drawable.button2 , RubikCube.class, RubikCubeMovement.class),
+  CUBE3 ( 3, R.drawable.button3 , RubikCube.class, RubikCubeMovement.class),
+  CUBE4 ( 4, R.drawable.button4 , RubikCube.class, RubikCubeMovement.class),
+  CUBE5 ( 5, R.drawable.button5 , RubikCube.class, RubikCubeMovement.class),
+  ;
+
+  public static final int VECTX = 0;  //
+  public static final int VECTY = 1;  // don't change this
+  public static final int VECTZ = 2;  //
+
+  public static final int LENGTH = values().length;
+  private final int mObjectSize, mIconID;
+  final Class<? extends RubikObject> mObjectClass;
+  final Class<? extends RubikObjectMovement> mObjectMovementClass;
+  private static final RubikObjectList[] objects;
+
+  static
+    {
+    int i = 0;
+    objects = new RubikObjectList[LENGTH];
+
+    for(RubikObjectList size: RubikObjectList.values())
+      {
+      objects[i] = size;
+      i++;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static RubikObjectList getObject(int ordinal)
+    {
+    return objects[ordinal];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikObjectList(int size, int iconID, Class<? extends RubikObject > object, Class<? extends RubikObjectMovement> movement)
+    {
+    mObjectSize = size;
+    mIconID     = iconID;
+    mObjectClass= object;
+    mObjectMovementClass = movement;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getIconID()
+    {
+    return mIconID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikObject create(Static4D quatCur, Static4D quatAcc, DistortedTexture texture, MeshFlat mesh, DistortedEffects effects)
+    {
+    return new RubikCube(mObjectSize, quatCur, quatAcc, texture, mesh, effects);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikObjectMovement getObjectMovementClass()
+    {
+    return new RubikCubeMovement();
+    }
+  }
diff --git a/src/main/java/org/distorted/object/RubikObjectMovement.java b/src/main/java/org/distorted/object/RubikObjectMovement.java
new file mode 100644
index 00000000..487f2658
--- /dev/null
+++ b/src/main/java/org/distorted/object/RubikObjectMovement.java
@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.object;
+
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static4D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class RubikObjectMovement
+  {
+  public abstract boolean faceTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera);
+  public abstract Static2D newRotation(Static4D rotatedTouchPoint);
+  public abstract float continueRotation(Static4D rotatedTouchPoint);
+  }
diff --git a/src/main/java/org/distorted/uistate/RubikStatePlay.java b/src/main/java/org/distorted/uistate/RubikStatePlay.java
index a41c77e1..1e089151 100644
--- a/src/main/java/org/distorted/uistate/RubikStatePlay.java
+++ b/src/main/java/org/distorted/uistate/RubikStatePlay.java
@@ -34,7 +34,7 @@ import android.widget.LinearLayout;
 import org.distorted.component.HorizontalNumberPicker;
 import org.distorted.magic.R;
 import org.distorted.magic.RubikActivity;
-import org.distorted.object.RubikObject;
+import org.distorted.object.RubikObjectList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -43,7 +43,7 @@ public class RubikStatePlay extends RubikStateAbstract
   private static final int MIN_SCRAMBLE =  1;
   private static final int DEF_SCRAMBLE =  1;
   public  static final int MAX_SCRAMBLE = 18;
-  private static final int DEF_BUTTON   = RubikObject.CUBE3.ordinal();
+  private static final int DEF_BUTTON   = RubikObjectList.CUBE3.ordinal();
 
   private HorizontalNumberPicker mPicker;
   private int mPickerValue;
@@ -79,13 +79,13 @@ public class RubikStatePlay extends RubikStateAbstract
     int padding = (int)(3*scale + 0.5f);
     ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size,size);
 
-    for(int i = 0; i< RubikObject.LENGTH; i++)
+    for(int i = 0; i< RubikObjectList.LENGTH; i++)
       {
       ImageButton button = new ImageButton(act);
       button.setLayoutParams(params);
       button.setId(i);
       button.setPadding(padding,0,padding,0);
-      int iconID = RubikObject.getObject(i).getIconID();
+      int iconID = RubikObjectList.getObject(i).getIconID();
       button.setImageResource(iconID);
       button.setOnClickListener(act);
       layoutBot.addView(button);
@@ -153,7 +153,7 @@ public class RubikStatePlay extends RubikStateAbstract
     {
     mButton = button;
 
-    for(int b = 0; b< RubikObject.LENGTH; b++)
+    for(int b = 0; b< RubikObjectList.LENGTH; b++)
       {
       Drawable d = act.findViewById(b).getBackground();
 
diff --git a/src/main/java/org/distorted/uistate/RubikStateSolving.java b/src/main/java/org/distorted/uistate/RubikStateSolving.java
index 4fbbc213..f64f9d2a 100644
--- a/src/main/java/org/distorted/uistate/RubikStateSolving.java
+++ b/src/main/java/org/distorted/uistate/RubikStateSolving.java
@@ -33,14 +33,14 @@ import java.util.Timer;
 import java.util.TimerTask;
 
 import static android.view.View.INVISIBLE;
-import static org.distorted.object.RubikObject.LENGTH;
+import static org.distorted.object.RubikObjectList.LENGTH;
 import static org.distorted.uistate.RubikStatePlay.MAX_SCRAMBLE;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class RubikStateSolving extends RubikStateAbstract
   {
-  private static final int NO_RECORD = Integer.MAX_VALUE;
+  private static final long NO_RECORD = Integer.MAX_VALUE;
 
   private TextView mTime;
   private Timer mTimer;
@@ -132,7 +132,7 @@ public class RubikStateSolving extends RubikStateAbstract
     for(int i=0; i<LENGTH; i++)
       for(int j=0; j<MAX_SCRAMBLE; j++)
         {
-        mRecords[i][j] = preferences.getInt("record_"+i+"_"+j, NO_RECORD );
+        mRecords[i][j] = preferences.getLong("record_"+i+"_"+j, NO_RECORD );
         }
     }
 
